Compare commits

..

3 Commits

Author SHA1 Message Date
LotP
18226decf1 check for HandleDesc (#112)
HandleDesc can be null, make sure to check for that.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/112
2026-05-25 13:43:35 +00:00
Babib3l
b2310823c9 Windows Fullscreen Fixes (#87)
This PR fixes two bugs introduced in #80 :
- Exiting fullscreen could exit the Ryujinx window  (https://github.com/Ryubing/Issues/issues/415)
- Toggling fullscreen on would move the window to the primary monitor

And two other bugs related to input view :
- Exiting fullscreen when window was previously in a non maximized state now conserves the window coordinates and size properly (https://github.com/Ryubing/Issues/issues/425)
- Opening the ryujinx app no makes the app grow slightly larger vertically on each launch (+31px on my 1080p monitor) (https://github.com/Ryubing/Issues/issues/425)

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/87
2026-05-25 12:09:00 +00:00
Babib3l
b62c58c2fe River : HLE: Make process identity explicit for service metadata resolution (#100)
This PR is the first in a batch of structural changes to Ryujinx.

**Changes**

- Added `ProcessIdentity` and `ProcessKind` to describe loaded programs by:
  - PID, program ID, application ID, program index, display version, process kind
- Stored identity metadata on `ProcessResult`.
- Added PID-based process lookup helpers to `ProcessLoader`.
- Updated HLE services to resolve application metadata through the caller PID instead of `Processes.ActiveApplication`.
- Added PTC/JIT disk cache initialization logging with PID, title ID, display version, selector, and enabled state.
- Added `ClientProcessId` property to ServiceCtx (/src/Ryujinx.HLE/HOS/ServiceCtx.cs) that uses the handle descriptor PId when available, falling back to `Process.Pid`.
- Updated 15 HLE service files to use `context.ClientProcessId` instead of `context.Process.Pid` for client process access, ensuring services correctly identify the calling process even when invoked via IPC with handle descriptors.

These changes make service metadata resolution more explicit and prepare the emulator for other structural changes later on.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/100
2026-05-25 12:08:31 +00:00
26 changed files with 234 additions and 56 deletions

View File

@@ -110,7 +110,7 @@ namespace ARMeilleure.Translation.PTC
Profiler.Wait();
Profiler.ClearEntries();
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t (title: {titleIdText}, version: '{displayVersion}', selector: '{cacheSelector}', enabled: {enabled}).");
if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
{
@@ -129,8 +129,6 @@ namespace ARMeilleure.Translation.PTC
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
_memoryMode = memoryMode;
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);

View File

@@ -118,7 +118,9 @@ namespace Ryujinx.HLE.HOS
}
}
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, _diskCacheSelector ?? "default");
string cacheSelector = _diskCacheSelector ?? "default";
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, cacheSelector);
return processContext;
}

View File

@@ -16,6 +16,7 @@ namespace Ryujinx.HLE.HOS
public IpcMessage Response { get; }
public BinaryReader RequestData { get; }
public BinaryWriter ResponseData { get; }
public ulong ClientProcessId => Request.HandleDesc is { HasPId: true } ? Request.HandleDesc.PId : Process.Pid;
public ServiceCtx(
Switch device,

View File

@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
// But since we use LibHac and we load one Application at a time, it's not necessary.
context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
context.ResponseData.Write((byte)context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties.UserAccountSwitchLock);
Logger.Stub?.PrintStub(LogClass.ServiceAcc);

View File

@@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
// OpenLibraryAppletSelfAccessor() -> object<nn::am::service::ILibraryAppletSelfAccessor>
public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletSelfAccessor(context));
MakeObject(context, new ILibraryAppletSelfAccessor(context, _pid));
return ResultCode.Success;
}

View File

@@ -8,9 +8,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
{
private readonly AppletStandalone _appletStandalone = new();
public ILibraryAppletSelfAccessor(ServiceCtx context)
public ILibraryAppletSelfAccessor(ServiceCtx context, ulong pid)
{
if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
ulong programId = context.Device.Processes.GetProcess(pid).ProgramId;
if (programId == 0x0100000000001009)
{
// Create MiiEdit data.
_appletStandalone = new AppletStandalone()
@@ -26,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
}
else
{
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
throw new NotImplementedException($"{programId} applet is not implemented.");
}
}

View File

@@ -44,8 +44,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private int _jitLoaded;
private readonly LibHac.HorizonClient _horizon;
private readonly ulong _pid;
public IApplicationFunctions(Horizon system)
public IApplicationFunctions(Horizon system, ulong pid)
{
// TODO: Find where they are signaled.
_gpuErrorDetectedSystemEvent = new KEvent(system.KernelContext);
@@ -55,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
_unknownEvent = new KEvent(system.KernelContext);
_horizon = system.LibHacHorizonManager.AmClient;
_pid = pid;
}
[CommandCmif(1)]
@@ -115,11 +117,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
public ResultCode EnsureSaveData(ServiceCtx context)
{
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
var process = context.Device.Processes.GetProcess(_pid);
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
ApplicationId applicationId = new(process.Identity.ApplicationId);
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty nacp = process.ApplicationControlProperties;
LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
@@ -139,7 +142,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// TODO: When above calls are implemented, switch to using ns:am
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
int supportedLanguages = (int)context.Device.Processes.GetProcess(_pid).ApplicationControlProperties.SupportedLanguageFlag;
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
@@ -182,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
public ResultCode GetDisplayVersion(ServiceCtx context)
{
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
context.ResponseData.Write(context.Device.Processes.GetProcess(_pid).ApplicationControlProperties.DisplayVersion);
return ResultCode.Success;
}
@@ -235,11 +238,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
ushort index = (ushort)context.RequestData.ReadUInt64();
long saveSize = context.RequestData.ReadInt64();
long journalSize = context.RequestData.ReadInt64();
var process = context.Device.Processes.GetProcess(_pid);
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
ApplicationId applicationId = new(process.Identity.ApplicationId);
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty nacp = process.ApplicationControlProperties;
LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);

View File

@@ -70,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
// GetApplicationFunctions() -> object<nn::am::service::IApplicationFunctions>
public ResultCode GetApplicationFunctions(ServiceCtx context)
{
MakeObject(context, new IApplicationFunctions(context.Device.System));
MakeObject(context, new IApplicationFunctions(context.Device.System, _pid));
return ResultCode.Success;
}

View File

@@ -27,13 +27,18 @@ namespace Ryujinx.HLE.HOS.Services.Arp
}
public static ApplicationLaunchProperty GetByPid(ServiceCtx context)
{
return GetByPid(context, context.ClientProcessId);
}
public static ApplicationLaunchProperty GetByPid(ServiceCtx context, ulong pid)
{
// TODO: Handle ApplicationLaunchProperty as array when pid will be supported and return the right item.
// For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented.
return new ApplicationLaunchProperty
{
TitleId = context.Device.Processes.ActiveApplication.ProgramId,
TitleId = context.Device.Processes.GetProcess(pid).ProgramId,
Version = 0x00,
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
UpdateGameStorageId = (byte)StorageId.None,

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.GetProcess(context.ClientProcessId).ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
context.ResponseData.WriteStruct(applicationAlbumEntry);
@@ -98,7 +98,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.GetProcess(context.ClientProcessId).ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
context.ResponseData.WriteStruct(applicationAlbumEntry);
@@ -143,7 +143,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.GetProcess(context.ClientProcessId).ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
context.ResponseData.WriteStruct(applicationAlbumEntry);

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Fatal.Types;
using Ryujinx.HLE.Loaders.Processes;
using System;
using System.Runtime.InteropServices;
using System.Text;
@@ -50,12 +51,13 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
private ResultCode ThrowFatalWithCpuContextImpl(ServiceCtx context, ResultCode resultCode, ulong pid, FatalPolicy fatalPolicy, ReadOnlySpan<byte> cpuContext)
{
ProcessResult process = context.Device.Processes.GetProcess(pid);
StringBuilder errorReport = new();
errorReport.AppendLine();
errorReport.AppendLine("ErrorReport log:");
errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}");
errorReport.AppendLine($"\tTitleId: {process.ProgramIdText}");
errorReport.AppendLine($"\tPid: {pid}");
errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
@@ -64,7 +66,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
{
errorReport.AppendLine("CPU Context:");
if (context.Device.Processes.ActiveApplication.Is64Bit)
if (process.Is64Bit)
{
CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];

View File

@@ -885,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{
byte programIndex = context.RequestData.ReadByte();
if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex)
if (context.Device.Processes.GetProcess(_pid).Identity.ProgramIndex != programIndex)
{
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
}

View File

@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
private bool CheckLocalCommunicationIdPermission(ServiceCtx context, ulong localCommunicationIdChecked)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty controlProperty = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
foreach (ulong localCommunicationId in controlProperty.LocalCommunicationId)
{
@@ -438,7 +438,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty controlProperty = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
scanFilter.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
}
@@ -613,7 +613,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty controlProperty = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
networkConfig.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
}
@@ -948,7 +948,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty controlProperty = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
networkInfo.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
}
@@ -1208,7 +1208,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
// TODO: Call nn::arp::GetApplicationLaunchProperty here when implemented.
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
NetworkClient.SetGameVersion(context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties.DisplayVersion);
resultCode = ResultCode.Success;
_nifmResultCode = resultCode;

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
return CountAddOnContentImpl(context, context.Device.Processes.GetProcess(pid).ProgramId);
}
[CommandCmif(3)]
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
return ListAddContentImpl(context, context.Device.Processes.GetProcess(pid).ProgramId);
}
[CommandCmif(4)] // 1.0.0-6.2.0
@@ -85,7 +85,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
return GetAddOnContentBaseIdImpl(context, context.Device.Processes.GetProcess(pid).ProgramId);
}
[CommandCmif(6)] // 1.0.0-6.2.0
@@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
return PrepareAddOnContentImpl(context, context.Device.Processes.GetProcess(pid).ProgramId);
}
[CommandCmif(8)] // 4.0.0+
@@ -138,7 +138,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
// TODO: Found where stored value is used.
ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.GetProcess(pid).ProgramId);
if (resultCode != ResultCode.Success)
{
@@ -310,7 +310,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
// NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId,
// If the call fails, it returns ResultCode.InvalidPid.
_addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId;
_addOnContentBaseId = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties.AddOnContentBaseId;
if (_addOnContentBaseId == 0)
{
@@ -324,7 +324,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
{
uint index = context.RequestData.ReadUInt32();
ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, titleId);
if (resultCode != ResultCode.Success)
{

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns
ulong position = context.Request.ReceiveBuff[0].Position;
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty nacp = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns
ulong position = context.Request.ReceiveBuff[0].Position;
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
ApplicationControlProperty nacp = context.Device.Processes.GetProcess(context.ClientProcessId).ApplicationControlProperties;
context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());

View File

@@ -48,21 +48,22 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
{
if ((_permissionFlag & 0x40) == 0)
{
ulong titleId = ApplicationLaunchProperty.GetByPid(context).TitleId;
ulong titleId = ApplicationLaunchProperty.GetByPid(context, _pid).TitleId;
if (titleId != 0)
{
_titleId = titleId;
var process = context.Device.Processes.GetProcess(_pid);
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
_ratingAge = new int[context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.Length];
_ratingAge = new int[process.ApplicationControlProperties.RatingAge.Length];
for (int i = 0; i < _ratingAge.Length; i++)
{
_ratingAge[i] = Convert.ToInt32(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge[i]);
_ratingAge[i] = Convert.ToInt32(process.ApplicationControlProperties.RatingAge[i]);
}
_parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag;
_parentalControlFlag = process.ApplicationControlProperties.ParentalControlFlag;
}
}

View File

@@ -2,6 +2,7 @@ using Ryujinx.Common;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
using Ryujinx.HLE.Loaders.Processes;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -31,7 +32,8 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
}
}
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability;
ProcessResult process = context.Device.Processes.GetProcess(context.ClientProcessId);
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)process.ApplicationControlProperties.PlayLogQueryCapability;
List<ulong> titleIds = [];
@@ -45,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
// Check if input title ids are in the whitelist.
foreach (ulong titleId in titleIds)
{
if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.AsReadOnlySpan().Contains(titleId))
if (!process.ApplicationControlProperties.PlayLogQueryableApplicationId.AsReadOnlySpan().Contains(titleId))
{
return (ResultCode)Am.ResultCode.ObjectInvalid;
}

View File

@@ -0,0 +1,34 @@
namespace Ryujinx.HLE.Loaders.Processes
{
public readonly struct ProcessIdentity
{
public ulong ProcessId { get; }
public ulong ProgramId { get; }
public ulong ApplicationId { get; }
public byte ProgramIndex { get; }
public string ProgramIdText { get; }
public string DisplayVersion { get; }
public ProcessKind Kind { get; }
public ProcessIdentity(
ulong processId,
ulong programId,
byte programIndex,
string displayVersion,
ProcessKind kind)
{
ProcessId = processId;
ProgramId = programId;
ProgramIndex = programIndex;
ApplicationId = programId & ~0xFul;
ProgramIdText = $"{programId:x16}";
DisplayVersion = displayVersion ?? string.Empty;
Kind = kind;
}
public override string ToString()
{
return $"{Kind} pid={ProcessId} program={ProgramIdText} application={ApplicationId:x16} index={ProgramIndex} version={DisplayVersion}";
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Ryujinx.HLE.Loaders.Processes
{
public enum ProcessKind
{
Unknown,
Application,
SystemApplication,
SystemApplet,
LibraryApplet,
Homebrew,
}
}

View File

@@ -93,6 +93,23 @@ namespace Ryujinx.HLE.Loaders.Processes
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
}
public bool TryGetProcess(ulong pid, out ProcessResult process)
{
return _processesByPid.TryGetValue(pid, out process);
}
public ProcessResult GetProcess(ulong pid)
{
if (_processesByPid.TryGetValue(pid, out ProcessResult process))
{
return process;
}
Logger.Warning?.Print(LogClass.Loader, $"Process metadata for pid {pid} was not found. Falling back to active application metadata.");
return ActiveApplication;
}
public bool LoadXci(string path, ulong applicationId)
{
FileStream stream = new(path, FileMode.Open, FileAccess.Read);

View File

@@ -436,6 +436,7 @@ namespace Ryujinx.HLE.Loaders.Processes
allowCodeMemoryForJit,
processContextFactory.DiskCacheLoadState,
process.Pid,
programIndex,
meta.MainThreadPriority,
meta.MainThreadStackSize,
device.System.State.DesiredTitleLanguage);

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.HLE.Loaders.Processes
{
public class ProcessResult
{
public static ProcessResult Failed => new(null, new BlitStruct<ApplicationControlProperty>(1), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish);
public static ProcessResult Failed => new(null, new BlitStruct<ApplicationControlProperty>(1), false, false, null, 0, 0, 0, 0, TitleLanguage.AmericanEnglish);
private readonly byte _mainThreadPriority;
private readonly uint _mainThreadStackSize;
@@ -28,6 +28,7 @@ namespace Ryujinx.HLE.Loaders.Processes
public readonly bool Is64Bit;
public readonly bool DiskCacheEnabled;
public readonly bool AllowCodeMemoryForJit;
public readonly ProcessIdentity Identity;
public ProcessResult(
MetaLoader metaLoader,
@@ -36,6 +37,7 @@ namespace Ryujinx.HLE.Loaders.Processes
bool allowCodeMemoryForJit,
IDiskCacheLoadState diskCacheLoadState,
ulong pid,
byte programIndex,
byte mainThreadPriority,
uint mainThreadStackSize,
TitleLanguage titleLanguage)
@@ -71,6 +73,7 @@ namespace Ryujinx.HLE.Loaders.Processes
ProgramId = programId;
ProgramIdText = $"{programId:x16}";
Is64Bit = metaLoader.IsProgram64Bit;
Identity = new ProcessIdentity(pid, programId, programIndex, DisplayVersion, GetProcessKind(programId));
}
else
@@ -84,6 +87,31 @@ namespace Ryujinx.HLE.Loaders.Processes
AllowCodeMemoryForJit = allowCodeMemoryForJit;
}
private static ProcessKind GetProcessKind(ulong programId)
{
if (programId == 0)
{
return ProcessKind.Application;
}
if (programId is >= 0x0100000000001000 and <= 0x0100000000001FFF)
{
return ProcessKind.SystemApplet;
}
if (programId is >= 0x0100000000000800 and <= 0x0100000000000FFF)
{
return ProcessKind.LibraryApplet;
}
if (programId <= 0x0100000000007FFF)
{
return ProcessKind.SystemApplication;
}
return ProcessKind.Application;
}
public bool Start(Switch device)
{
device.Configuration.ContentManager.LoadEntries(device);
@@ -109,6 +137,7 @@ namespace Ryujinx.HLE.Loaders.Processes
: device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?";
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
Logger.Info?.Print(LogClass.Loader, $"Process identity: {Identity}");
return true;
}

View File

@@ -63,6 +63,18 @@ namespace Ryujinx.Ava.UI.Helpers
}
}
[StructLayout(LayoutKind.Sequential)]
public struct NativeRect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width => Right - Left;
public int Height => Bottom - Top;
}
public static nint CreateEmptyCursor()
{
return CreateCursor(nint.Zero, 0, 0, 1, 1, [0xFF], [0x00]);
@@ -119,6 +131,10 @@ namespace Ryujinx.Ava.UI.Helpers
[LibraryImport("user32.dll", SetLastError = true)]
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
[LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetWindowRect(nint hWnd, out NativeRect lpRect);
[LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetWindowPos(

View File

@@ -2060,6 +2060,12 @@ namespace Ryujinx.Ava.UI.ViewModels
}
private nint _savedWindowStyle;
private WindowState _savedWindowState;
private PixelPoint _savedWindowPosition;
private double _savedWindowWidth;
private double _savedWindowHeight;
private Win32NativeInterop.NativeRect _savedWindowRect;
private bool _savedWindowRectValid;
[SupportedOSPlatform("windows")]
private void MakeWindowFullscreen()
@@ -2067,19 +2073,36 @@ namespace Ryujinx.Ava.UI.ViewModels
nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero;
if (hwnd == nint.Zero) return;
PixelPoint windowCenter = new(
Window.Position.X + (int)(Window.Bounds.Width / 2),
Window.Position.Y + (int)(Window.Bounds.Height / 2));
Avalonia.Platform.Screen? screen =
Window.Screens.ScreenFromVisual(Window) ??
Window.Screens.ScreenFromPoint(windowCenter) ??
Window.Screens.Primary;
if (screen == null)
{
return; // Can't determine screen size, don't attempt fullscreen
}
// Save current style and placement
_savedWindowStyle = Win32NativeInterop.GetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE);
_savedWindowState = WindowState;
_savedWindowPosition = Window.Position;
_savedWindowWidth = Window.Width;
_savedWindowHeight = Window.Height;
_savedWindowRectValid = Win32NativeInterop.GetWindowRect(hwnd, out _savedWindowRect);
// Remove window chrome: WS_OVERLAPPEDWINDOW -> WS_POPUP | WS_VISIBLE
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE,
unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE)));
// TODO: why is this nullable
Avalonia.Platform.Screen? screen = Window.Screens.ScreenFromVisual(Window);
int w = screen?.Bounds.Width ?? 0;
int h = screen?.Bounds.Height ?? 0;
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, w, h,
int w = screen.Bounds.Width;
int h = screen.Bounds.Height;
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, screen.Bounds.X, screen.Bounds.Y, w, h,
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED);
WindowState = WindowState.FullScreen;
@@ -2094,10 +2117,34 @@ namespace Ryujinx.Ava.UI.ViewModels
// Restore original window style
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, _savedWindowStyle);
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0,
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE |
Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE);
if (_savedWindowState is WindowState.Maximized)
{
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0,
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE |
Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE);
}
else if (_savedWindowRectValid)
{
Dispatcher.UIThread.Post(() => RestoreSavedWindowRect(hwnd), DispatcherPriority.Background);
}
else
{
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0,
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE |
Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE);
}
}
[SupportedOSPlatform("windows")]
private void RestoreSavedWindowRect(nint hwnd)
{
Window.Position = _savedWindowPosition;
Window.Width = _savedWindowWidth;
Window.Height = _savedWindowHeight;
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, _savedWindowRect.Left, _savedWindowRect.Top,
_savedWindowRect.Width, _savedWindowRect.Height,
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED);
}
public static void SaveConfig()

View File

@@ -48,6 +48,8 @@ namespace Ryujinx.Ava.UI.Windows
private bool _isLoading;
private bool _applicationsLoadedOnce;
private double _windowStartupWidthDelta;
private double _windowStartupHeightDelta;
private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad;
@@ -477,8 +479,8 @@ namespace Ryujinx.Ava.UI.Windows
{
// Since scaling is being applied to the loaded settings from disk (see SetWindowSizePosition() above), scaling should be removed from width/height before saving out to disk
// as well - otherwise anyone not using a 1.0 scale factor their window will increase in size with every subsequent launch of the program when scaling is applied (Nov. 14, 2024)
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)((Height - _windowStartupHeightDelta) / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)((Width - _windowStartupWidthDelta) / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
@@ -493,6 +495,9 @@ namespace Ryujinx.Ava.UI.Windows
Initialize();
_windowStartupWidthDelta = Math.Max(0, Width - ViewModel.WindowWidth);
_windowStartupHeightDelta = Math.Max(0, Height - ViewModel.WindowHeight);
PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
ViewModel.Initialize(