Compare commits

...

13 Commits

Author SHA1 Message Date
Shyanne
09d4bfe6d8 Merge branch 'master' into 20-empty-nca-lockup 2026-01-28 00:34:04 -05:00
GreemDev
5ed94c365b add a stack trace for the catch branch of AppleHardwareDeviceDriver.IsSupported 2026-01-27 17:52:45 -06:00
GreemDev
fef93a453a [ci skip] replace all usages of IntPtr with nint 2026-01-27 17:41:46 -06:00
Shyanne
2274a32813 Updated LoadAndSaveMetadata to return Optional type (but for real this time)
- fixed a typo i found
- awaiting dialog on invalid unpacked error
-
2025-12-30 17:06:21 -05:00
Shyanne
34190c9184 Moved null title id check to LoadAndSaveMetadata 2025-12-29 12:09:44 -05:00
Shyanne
18501d01f3 Merge branch '20-empty-nca-lockup' of https://git.ryujinx.app/sh0inx/ryujinx into 20-empty-nca-lockup 2025-12-29 00:31:45 -05:00
Shyanne
6780ff0d8a 👍 2025-12-29 00:28:50 -05:00
Shyanne
3cc02ebaef maybe don't dispose of the gpu when it doesnt exist 2025-12-28 16:54:21 -05:00
Shyanne
6938265651 Merge branch 'master' into 20-empty-nca-lockup 2025-12-28 14:16:39 -05:00
Shyanne
f6328ebb69 Amended typos and updated commentary 2025-12-28 13:16:07 -06:00
Shyanne
445924102e Addressed empty NCA lockup
- Updated LoadGuestApplication to use a CancellationTokenSource so we can properly asynchronously cancel and not hang (other things could listen to this too, or cancel it).
- Moved PrepareLoadScreen() to later in the pipeline (because cancelling LoadGuestApplication causes issues).
- Added Metadata read/write logging.
- Made AppHost inherit Disposable interface so that the garbage collector kicks in (side effect: made private Dispose() public)
- Added a WaitHandle to wait on either gpuDoneEvent or gpuCTS.Cancel event (LoadGuestApplication cancellation).
- Set invalid title ID for metadata.
2025-12-28 13:16:07 -06:00
Shyanne
cf72e189b7 Amended typos and updated commentary 2025-12-27 23:04:20 -05:00
Shyanne
8bd290cc57 Addressed empty NCA lockup
- Updated LoadGuestApplication to use a CancellationTokenSource so we can properly asynchronously cancel and not hang (other things could listen to this too, or cancel it).
- Moved PrepareLoadScreen() to later in the pipeline (because cancelling LoadGuestApplication causes issues).
- Added Metadata read/write logging.
- Made AppHost inherit Disposable interface so that the garbage collector kicks in (side effect: made private Dispose() public)
- Added a WaitHandle to wait on either gpuDoneEvent or gpuCTS.Cancel event (LoadGuestApplication cancellation).
- Set invalid title ID for metadata.
2025-12-27 22:34:48 -05:00
20 changed files with 185 additions and 113 deletions

View File

@@ -168,7 +168,7 @@ namespace ARMeilleure.Common
{ {
_allocated.Dispose(); _allocated.Dispose();
foreach (IntPtr page in _pages.Values) foreach (nint page in _pages.Values)
{ {
NativeAllocator.Instance.Free((void*)page); NativeAllocator.Instance.Free((void*)page);
} }

View File

@@ -44,12 +44,12 @@ namespace Ryujinx.Audio.Backends.Apple
int result = AudioQueueNewOutput( int result = AudioQueueNewOutput(
ref format, ref format,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
0, 0,
out IntPtr testQueue); out nint testQueue);
if (result == 0) if (result == 0)
{ {
@@ -95,12 +95,12 @@ namespace Ryujinx.Audio.Backends.Apple
GetAudioFormat(SampleFormat.PcmInt16, Constants.TargetSampleRate, 2); GetAudioFormat(SampleFormat.PcmInt16, Constants.TargetSampleRate, 2);
int result = AudioQueueNewOutput( int result = AudioQueueNewOutput(
ref format, ref format,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
0, 0,
out IntPtr testQueue); out nint testQueue);
if (result == 0) if (result == 0)
{ {
@@ -110,8 +110,9 @@ namespace Ryujinx.Audio.Backends.Apple
return false; return false;
} }
catch catch (Exception e)
{ {
Logger.Error?.Print(LogClass.Audio, $"Failed to check if AudioToolbox is supported: {e.Message}\n{e.StackTrace}");
return false; return false;
} }
} }

View File

@@ -1,15 +1,12 @@
using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common; using Ryujinx.Audio.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Ryujinx.Audio.Backends.Apple.Native;
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox; using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
using static Ryujinx.Audio.Backends.Apple.AppleHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.Apple namespace Ryujinx.Audio.Backends.Apple
{ {
@@ -27,8 +24,8 @@ namespace Ryujinx.Audio.Backends.Apple
private readonly AudioQueueOutputCallback _callbackDelegate; private readonly AudioQueueOutputCallback _callbackDelegate;
private readonly GCHandle _gcHandle; private readonly GCHandle _gcHandle;
private IntPtr _audioQueue; private nint _audioQueue;
private readonly IntPtr[] _audioQueueBuffers = new IntPtr[NumBuffers]; private readonly nint[] _audioQueueBuffers = new nint[NumBuffers];
private readonly int[] _bufferBytesFilled = new int[NumBuffers]; private readonly int[] _bufferBytesFilled = new int[NumBuffers];
private readonly int _bytesPerFrame; private readonly int _bytesPerFrame;
@@ -41,9 +38,9 @@ namespace Ryujinx.Audio.Backends.Apple
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void AudioQueueOutputCallback( private delegate void AudioQueueOutputCallback(
IntPtr userData, nint userData,
IntPtr audioQueue, nint audioQueue,
IntPtr buffer); nint buffer);
public AppleHardwareDeviceSession( public AppleHardwareDeviceSession(
AppleHardwareDeviceDriver driver, AppleHardwareDeviceDriver driver,
@@ -72,15 +69,15 @@ namespace Ryujinx.Audio.Backends.Apple
RequestedSampleRate, RequestedSampleRate,
RequestedChannelCount); RequestedChannelCount);
IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackDelegate); nint callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackDelegate);
IntPtr userData = GCHandle.ToIntPtr(_gcHandle); nint userData = GCHandle.ToIntPtr(_gcHandle);
int result = AudioQueueNewOutput( int result = AudioQueueNewOutput(
ref format, ref format,
callbackPtr, callbackPtr,
userData, userData,
IntPtr.Zero, nint.Zero,
IntPtr.Zero, nint.Zero,
0, 0,
out _audioQueue); out _audioQueue);
@@ -102,7 +99,7 @@ namespace Ryujinx.Audio.Backends.Apple
} }
} }
private unsafe void PrimeBuffer(IntPtr bufferPtr, int bufferIndex) private unsafe void PrimeBuffer(nint bufferPtr, int bufferIndex)
{ {
AudioQueueBuffer* buffer = (AudioQueueBuffer*)bufferPtr; AudioQueueBuffer* buffer = (AudioQueueBuffer*)bufferPtr;
@@ -126,12 +123,12 @@ namespace Ryujinx.Audio.Backends.Apple
buffer->AudioDataByteSize = (uint)capacityBytes; buffer->AudioDataByteSize = (uint)capacityBytes;
_bufferBytesFilled[bufferIndex] = bytesToRead; _bufferBytesFilled[bufferIndex] = bytesToRead;
AudioQueueEnqueueBuffer(_audioQueue, bufferPtr, 0, IntPtr.Zero); AudioQueueEnqueueBuffer(_audioQueue, bufferPtr, 0, nint.Zero);
} }
private void OutputCallback(IntPtr userData, IntPtr audioQueue, IntPtr bufferPtr) private void OutputCallback(nint userData, nint audioQueue, nint bufferPtr)
{ {
if (!_started || bufferPtr == IntPtr.Zero) if (!_started || bufferPtr == nint.Zero)
return; return;
int bufferIndex = Array.IndexOf(_audioQueueBuffers, bufferPtr); int bufferIndex = Array.IndexOf(_audioQueueBuffers, bufferPtr);
@@ -176,7 +173,7 @@ namespace Ryujinx.Audio.Backends.Apple
} }
} }
private unsafe void ApplyVolume(IntPtr dataPtr, int byteSize) private unsafe void ApplyVolume(nint dataPtr, int byteSize)
{ {
float volume = Math.Clamp(_volume * _driver.Volume, 0f, 1f); float volume = Math.Clamp(_volume * _driver.Volume, 0f, 1f);
if (volume >= 0.999f) if (volume >= 0.999f)
@@ -226,7 +223,7 @@ namespace Ryujinx.Audio.Backends.Apple
return; return;
_started = true; _started = true;
AudioQueueStart(_audioQueue, IntPtr.Zero); AudioQueueStart(_audioQueue, nint.Zero);
} }
} }
@@ -265,11 +262,11 @@ namespace Ryujinx.Audio.Backends.Apple
{ {
Stop(); Stop();
if (_audioQueue != IntPtr.Zero) if (_audioQueue != nint.Zero)
{ {
AudioQueueStop(_audioQueue, true); AudioQueueStop(_audioQueue, true);
AudioQueueDispose(_audioQueue, true); AudioQueueDispose(_audioQueue, true);
_audioQueue = IntPtr.Zero; _audioQueue = nint.Zero;
} }
if (_gcHandle.IsAllocated) if (_gcHandle.IsAllocated)

View File

@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
unsafe unsafe
{ {
int* frameCountPtr = &nativeFrameCount; int* frameCountPtr = &nativeFrameCount;
IntPtr* arenasPtr = &arenas; nint* arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr)); CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
frameCount = *frameCountPtr; frameCount = *frameCountPtr;

View File

@@ -30,9 +30,9 @@ namespace ARMeilleure.Common
/// <summary> /// <summary>
/// Base address for the page. /// Base address for the page.
/// </summary> /// </summary>
public readonly IntPtr Address; public readonly nint Address;
public AddressTablePage(bool isSparse, IntPtr address) public AddressTablePage(bool isSparse, nint address)
{ {
IsSparse = isSparse; IsSparse = isSparse;
Address = address; Address = address;
@@ -47,20 +47,20 @@ namespace ARMeilleure.Common
public readonly SparseMemoryBlock Block; public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent; private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit) public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit)
{ {
SparseMemoryBlock block = new(size, pageInit, null); SparseMemoryBlock block = new(size, pageInit, null);
_trackingEvent = (address, size, write) => _trackingEvent = (address, size, write) =>
{ {
ulong pointer = (ulong)block.Block.Pointer + address; ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((IntPtr)pointer); ensureMapped((nint)pointer);
return pointer; return pointer;
}; };
bool added = NativeSignalHandler.AddTrackedRegion( bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer, (nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (IntPtr)block.Block.Size), (nuint)(block.Block.Pointer + (nint)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent)); Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added) if (!added)
@@ -116,7 +116,7 @@ namespace ARMeilleure.Common
} }
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr Base public nint Base
{ {
get get
{ {
@@ -124,7 +124,7 @@ namespace ARMeilleure.Common
lock (_pages) lock (_pages)
{ {
return (IntPtr)GetRootPage(); return (nint)GetRootPage();
} }
} }
} }
@@ -240,7 +240,7 @@ namespace ARMeilleure.Common
long index = Levels[^1].GetValue(address); long index = Levels[^1].GetValue(address);
EnsureMapped((IntPtr)(page + index)); EnsureMapped((nint)(page + index));
return ref page[index]; return ref page[index];
} }
@@ -284,7 +284,7 @@ namespace ARMeilleure.Common
/// Ensure the given pointer is mapped in any overlapping sparse reservations. /// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary> /// </summary>
/// <param name="ptr">Pointer to be mapped</param> /// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(IntPtr ptr) private void EnsureMapped(nint ptr)
{ {
if (Sparse) if (Sparse)
{ {
@@ -299,7 +299,7 @@ namespace ARMeilleure.Common
{ {
SparseMemoryBlock sparse = reserved.Block; SparseMemoryBlock sparse = reserved.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size) if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
{ {
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer)); sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
@@ -319,15 +319,15 @@ namespace ARMeilleure.Common
/// </summary> /// </summary>
/// <param name="level">Level to get the fill value for</param> /// <param name="level">Level to get the fill value for</param>
/// <returns>The fill value</returns> /// <returns>The fill value</returns>
private IntPtr GetFillValue(int level) private nint GetFillValue(int level)
{ {
if (_fillBottomLevel != null && level == Levels.Length - 2) if (_fillBottomLevel != null && level == Levels.Length - 2)
{ {
return (IntPtr)_fillBottomLevelPtr; return (nint)_fillBottomLevelPtr;
} }
else else
{ {
return IntPtr.Zero; return nint.Zero;
} }
} }
@@ -379,7 +379,7 @@ namespace ARMeilleure.Common
/// <param name="fill">Fill value</param> /// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param> /// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns> /// <returns>Allocated block</returns>
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
{ {
int size = sizeof(T) * length; int size = sizeof(T) * length;
@@ -405,7 +405,7 @@ namespace ARMeilleure.Common
} }
} }
page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset); page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset);
_sparseReservedOffset += (ulong)size; _sparseReservedOffset += (ulong)size;
@@ -413,7 +413,7 @@ namespace ARMeilleure.Common
} }
else else
{ {
IntPtr address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
page = new AddressTablePage(false, address); page = new AddressTablePage(false, address);
Span<T> span = new((void*)page.Address, length); Span<T> span = new((void*)page.Address, length);

View File

@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel; bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
IntPtr hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0; nint hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size)) if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
{ {

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
@@ -114,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
cbs.AddDependant(this); cbs.AddDependant(this);
// We need to add a dependency on the command buffer to all objects this object // We need to add a dependency on the command buffer to all objects this object
// references aswell. // references as well.
if (_referencedObjs != null) if (_referencedObjs != null)
{ {
for (int i = 0; i < _referencedObjs.Length; i++) for (int i = 0; i < _referencedObjs.Length; i++)
@@ -176,6 +177,8 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
// This can somehow become -1.
// Logger.Info?.PrintMsg(LogClass.Gpu, $"_referenceCount: {_referenceCount}");
Debug.Assert(_referenceCount >= 0); Debug.Assert(_referenceCount >= 0);
} }

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
public static void Initialize() public static void Initialize()
{ {
IntPtr configSize = (nint)Marshal.SizeOf<MVKConfiguration>(); nint configSize = (nint)Marshal.SizeOf<MVKConfiguration>();
vkGetMoltenVKConfigurationMVK(nint.Zero, out MVKConfiguration config, configSize); vkGetMoltenVKConfigurationMVK(nint.Zero, out MVKConfiguration config, configSize);

View File

@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray(); enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
} }
IntPtr appName = Marshal.StringToHGlobalAnsi(AppName); nint appName = Marshal.StringToHGlobalAnsi(AppName);
ApplicationInfo applicationInfo = new() ApplicationInfo applicationInfo = new()
{ {
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api) internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
{ {
IntPtr appName = Marshal.StringToHGlobalAnsi(AppName); nint appName = Marshal.StringToHGlobalAnsi(AppName);
ApplicationInfo applicationInfo = new() ApplicationInfo applicationInfo = new()
{ {

View File

@@ -1059,7 +1059,7 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
public static bool AreKeysAlredyPresent(string pathToCheck) public static bool AreKeysAlreadyPresent(string pathToCheck)
{ {
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"]; string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames) foreach (string file in fileNames)

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
public nint Handle => IntPtr.Zero; public nint Handle => nint.Zero;
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;

View File

@@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.HLE.Loaders.Processes.Extensions;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO; using System.IO;
using Path = System.IO.Path; using Path = System.IO.Path;
@@ -27,10 +28,15 @@ namespace Ryujinx.HLE.Loaders.Processes
private ulong _latestPid; private ulong _latestPid;
public ProcessResult ActiveApplication public ProcessResult? ActiveApplication
{ {
get get
{ {
return _processesByPid.GetValueOrDefault(_latestPid);
// Using this if statement locks up the UI and prevents a new game from loading.
// Haven't quite deduced why yet.
if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value)) if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value))
throw new RyujinxException( throw new RyujinxException(
$"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?"); $"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?");

View File

@@ -4,7 +4,6 @@ using LibHac.Ns;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.Loaders.Processes namespace Ryujinx.HLE.Loaders.Processes
@@ -52,6 +51,7 @@ namespace Ryujinx.HLE.Loaders.Processes
if (metaLoader is not null) if (metaLoader is not null)
{ {
Logger.Info?.Print(LogClass.Application,$"metaLoader: {metaLoader}");
ulong programId = metaLoader.ProgramId; ulong programId = metaLoader.ProgramId;
Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString(); Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString();
@@ -73,6 +73,13 @@ namespace Ryujinx.HLE.Loaders.Processes
Is64Bit = metaLoader.IsProgram64Bit; Is64Bit = metaLoader.IsProgram64Bit;
} }
else
{
Logger.Error?.Print(LogClass.Application,$"metaLoader is null !!!");
ProcessId = 0;
return;
}
DiskCacheEnabled = diskCacheEnabled; DiskCacheEnabled = diskCacheEnabled;
AllowCodeMemoryForJit = allowCodeMemoryForJit; AllowCodeMemoryForJit = allowCodeMemoryForJit;
} }

View File

@@ -159,7 +159,7 @@ namespace Ryujinx.Memory.WindowsShared
{ {
SplitForMap((ulong)location, (ulong)size, srcOffset); SplitForMap((ulong)location, (ulong)size, srcOffset);
IntPtr ptr = WindowsApi.MapViewOfFile3( nint ptr = WindowsApi.MapViewOfFile3(
sharedMemory, sharedMemory,
WindowsApi.CurrentProcessHandle, WindowsApi.CurrentProcessHandle,
location, location,

View File

@@ -227,7 +227,7 @@ namespace Ryujinx.Tests.Memory
// Create some info to be used for managing the native writing loop. // Create some info to be used for managing the native writing loop.
int stateSize = Unsafe.SizeOf<NativeWriteLoopState>(); int stateSize = Unsafe.SizeOf<NativeWriteLoopState>();
IntPtr statePtr = Marshal.AllocHGlobal(stateSize); nint statePtr = Marshal.AllocHGlobal(stateSize);
Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize); Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize);
ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr); ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr);

View File

@@ -62,7 +62,7 @@ using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.Ava.Systems namespace Ryujinx.Ava.Systems
{ {
internal class AppHost internal class AppHost : IDisposable
{ {
private const int CursorHideIdleTime = 5; // Hide Cursor seconds. private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
@@ -438,7 +438,7 @@ namespace Ryujinx.Ava.Systems
SaveBitmapAsPng(bitmapToSave, path); SaveBitmapAsPng(bitmapToSave, path);
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); Logger.Notice.Print(LogClass.Application, $"Screenshot saved to '{path}'.", "Screenshot");
} }
}); });
} }
@@ -611,26 +611,39 @@ namespace Ryujinx.Ava.Systems
_isActive = false; _isActive = false;
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
_gpuDoneEvent.WaitOne();
_gpuDoneEvent.Dispose();
DisplaySleep.Restore(); DisplaySleep.Restore();
NpadManager.Dispose(); NpadManager.Dispose();
TouchScreenManager.Dispose(); TouchScreenManager.Dispose();
Device.Dispose(); Device.Dispose();
DisposeGpu(); // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
// If the GPU has no work and is cancelled, we need to handle that as well.
WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle });
_gpuCancellationTokenSource.Dispose();
// Waiting for work to be finished before we dispose.
if (_renderingStarted)
{
Device.Gpu.WaitUntilGpuReady();
}
_gpuDoneEvent.Dispose();
DisposeGpu();
AppExit?.Invoke(this, EventArgs.Empty); AppExit?.Invoke(this, EventArgs.Empty);
} }
private void Dispose() // MUST be public to inherit from IDisposable
public void Dispose()
{ {
if (Device.Processes != null) if (Device.Processes != null)
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed); {
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication?.ProgramIdText,
_playTimer.Elapsed);
}
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
@@ -646,7 +659,6 @@ namespace Ryujinx.Ava.Systems
_topLevel.PointerExited -= TopLevel_PointerExited; _topLevel.PointerExited -= TopLevel_PointerExited;
_gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
_chrono.Stop(); _chrono.Stop();
_playTimer.Stop(); _playTimer.Stop();
@@ -672,6 +684,12 @@ namespace Ryujinx.Ava.Systems
} }
else else
{ {
// No use waiting on something that never started work
if (_renderingStarted)
{
Device.Gpu.WaitUntilGpuReady();
}
Device.DisposeGpu(); Device.DisposeGpu();
} }
} }
@@ -686,7 +704,7 @@ namespace Ryujinx.Ava.Systems
_cursorState = CursorStates.ForceChangeCursor; _cursorState = CursorStates.ForceChangeCursor;
} }
public async Task<bool> LoadGuestApplication(BlitStruct<ApplicationControlProperty>? customNacpData = null) public async Task LoadGuestApplication(CancellationTokenSource cts, BlitStruct<ApplicationControlProperty>? customNacpData = null)
{ {
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now; DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
@@ -715,7 +733,8 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError); await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
} }
@@ -724,10 +743,11 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError); await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
// Tell the user that we installed a firmware for them. // Tell the user that we installed firmware for them.
if (userError is UserError.NoFirmware) if (userError is UserError.NoFirmware)
{ {
firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -747,7 +767,8 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError); await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
} }
} }
@@ -762,7 +783,8 @@ namespace Ryujinx.Ava.Systems
{ {
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
} }
else if (Directory.Exists(ApplicationPath)) else if (Directory.Exists(ApplicationPath))
@@ -782,20 +804,24 @@ namespace Ryujinx.Ava.Systems
if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
{ {
await ContentDialogHelper.CreateErrorDialog(
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
} }
else else
{ {
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
if (!Device.LoadCart(ApplicationPath)) if (!Device.LoadCart(ApplicationPath))
{ {
await ContentDialogHelper.CreateErrorDialog(
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
Device.Dispose(); Device.Dispose();
cts.Cancel();
return false; throw new OperationCanceledException(cts.Token);
} }
} }
} }
@@ -813,7 +839,8 @@ namespace Ryujinx.Ava.Systems
{ {
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
break; break;
@@ -826,7 +853,8 @@ namespace Ryujinx.Ava.Systems
{ {
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
break; break;
@@ -840,7 +868,8 @@ namespace Ryujinx.Ava.Systems
{ {
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
break; break;
@@ -855,7 +884,8 @@ namespace Ryujinx.Ava.Systems
{ {
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
@@ -864,7 +894,8 @@ namespace Ryujinx.Ava.Systems
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
break; break;
@@ -873,19 +904,18 @@ namespace Ryujinx.Ava.Systems
} }
else else
{ {
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NSO/NRO file.");
Device.Dispose(); Device.Dispose();
return false; cts.Cancel();
throw new OperationCanceledException(cts.Token);
} }
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame() appMetadata => appMetadata.UpdatePreGame()
); );
_playTimer.Start(); _playTimer.Start();
return true;
} }
internal void Resume() internal void Resume()
@@ -895,7 +925,7 @@ namespace Ryujinx.Ava.Systems
_viewModel.IsPaused = false; _viewModel.IsPaused = false;
_playTimer.Start(); _playTimer.Start();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
} }
internal void Pause() internal void Pause()
@@ -905,7 +935,7 @@ namespace Ryujinx.Ava.Systems
_viewModel.IsPaused = true; _viewModel.IsPaused = true;
_playTimer.Stop(); _playTimer.Stop();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
} }
private void InitEmulatedSwitch() private void InitEmulatedSwitch()
@@ -1104,7 +1134,9 @@ namespace Ryujinx.Ava.Systems
// Make sure all commands in the run loop are fully executed before leaving the loop. // Make sure all commands in the run loop are fully executed before leaving the loop.
if (Device.Gpu.Renderer is ThreadedRenderer threaded) if (Device.Gpu.Renderer is ThreadedRenderer threaded)
{ {
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
threaded.FlushThreadedCommands(); threaded.FlushThreadedCommands();
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
} }
_gpuDoneEvent.Set(); _gpuDoneEvent.Set();

View File

@@ -849,7 +849,8 @@ namespace Ryujinx.Ava.Systems.AppLibrary
foreach (ApplicationData installedApplication in Applications.Items) foreach (ApplicationData installedApplication in Applications.Items)
{ {
temporary += LoadAndSaveMetaData(installedApplication.IdString).TimePlayed; // this should always exist... should...
temporary += LoadAndSaveMetaData(installedApplication.IdString).Value.TimePlayed;
} }
TotalTimePlayed = temporary; TotalTimePlayed = temporary;
@@ -1159,8 +1160,14 @@ namespace Ryujinx.Ava.Systems.AppLibrary
ApplicationCountUpdated?.Invoke(null, e); ApplicationCountUpdated?.Invoke(null, e);
} }
public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null) public static Gommon.Optional<ApplicationMetadata> LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
{ {
if (titleId is null)
{
Logger.Warning?.PrintMsg(LogClass.Application, "Cannot save metadata because title ID is invalid.");
return null;
}
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui"); string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
string metadataFile = Path.Combine(metadataFolder, "metadata.json"); string metadataFile = Path.Combine(metadataFolder, "metadata.json");
@@ -1168,6 +1175,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (!File.Exists(metadataFile)) if (!File.Exists(metadataFile))
{ {
Logger.Info?.Print(LogClass.Application, $"Metadata file does not exist. Creating metadata for {titleId}...");
Directory.CreateDirectory(metadataFolder); Directory.CreateDirectory(metadataFolder);
appMetadata = new ApplicationMetadata(); appMetadata = new ApplicationMetadata();
@@ -1177,12 +1185,12 @@ namespace Ryujinx.Ava.Systems.AppLibrary
try try
{ {
Logger.Debug?.Print(LogClass.Application, $"Deserializing metadata for {titleId}...");
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata); appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
} }
catch (JsonException) catch (JsonException)
{ {
Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults."); Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
appMetadata = new ApplicationMetadata(); appMetadata = new ApplicationMetadata();
} }

View File

@@ -82,7 +82,7 @@ namespace Ryujinx.Ava.Systems
public static void Use(Optional<string> titleId) public static void Use(Optional<string> titleId)
{ {
if (titleId.TryGet(out string tid)) if (titleId.TryGet(out string tid) && Switch.Shared.Processes.ActiveApplication is not null)
SwitchToPlayingState( SwitchToPlayingState(
ApplicationLibrary.LoadAndSaveMetaData(tid), ApplicationLibrary.LoadAndSaveMetaData(tid),
Switch.Shared.Processes.ActiveApplication Switch.Shared.Processes.ActiveApplication

View File

@@ -57,8 +57,15 @@ namespace Ryujinx.Ava.UI.Models
} }
else else
{ {
ApplicationMetadata appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); Gommon.Optional<ApplicationMetadata> appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
Title = appMetadata.Title ?? TitleIdString; if (appMetadata != null)
{
Title = appMetadata.Value.Title ?? TitleIdString;
}
else
{
Title = "<INVALID>";
}
} }
Task.Run(() => Task.Run(() =>

View File

@@ -1029,7 +1029,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string dialogMessage = string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
if (ContentManager.AreKeysAlredyPresent(systemDirectory)) if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
{ {
dialogMessage += dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
@@ -1703,11 +1703,6 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime(); Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path,
ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen();
RendererHostControl = new RendererHost(); RendererHostControl = new RendererHost();
AppHost = new AppHost( AppHost = new AppHost(
@@ -1722,18 +1717,34 @@ namespace Ryujinx.Ava.UI.ViewModels
this, this,
TopLevel); TopLevel);
if (!await AppHost.LoadGuestApplication(customNacpData)) CancellationTokenSource cts = new CancellationTokenSource();
try
{ {
await AppHost.LoadGuestApplication(cts, customNacpData);
}
catch (OperationCanceledException exception)
{
Logger.Info?.Print(LogClass.Application,
"LoadGuestApplication was interrupted !!! " + exception.Message);
AppHost.DisposeContext(); AppHost.DisposeContext();
AppHost = null; AppHost = null;
return; return;
} }
finally
{
cts.Dispose();
}
CanUpdate = false; CanUpdate = false;
application.Name ??= AppHost.Device.Processes.ActiveApplication.Name; application.Name ??= AppHost.Device.Processes.ActiveApplication.Name;
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path,
ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen();
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, application.Name); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, application.Name);
SwitchToRenderer(startFullscreen); SwitchToRenderer(startFullscreen);