mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-18 10:41:49 +00:00
UI: LoadGuestApplication asynchronous cancellation (#1)
Fixed LoadGuestApplication hanging when cancelled. Since startup procedure has technically changed, we should consider testing this with a variety of game formats to ensure regressions do not occur. Closes [#20](https://github.com/Ryubing/Issues/issues/20) Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/1
This commit is contained in:
parent
2a4eb8c529
commit
8705fabdb0
8 changed files with 129 additions and 55 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(() =>
|
||||||
|
|
|
||||||
|
|
@ -1760,11 +1760,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(
|
||||||
|
|
@ -1779,18 +1774,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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue