mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 08:59:04 -06:00 
			
		
		
		
	Avoid race conditions while launching games directly from the command line (#7116)
* optimization: Load application metadata only for applications with IDs * Load applications when necessary This prevents loading applications when launching an application directly from the command line (or a shortcut). Instead, applications will be loaded after the emulation was stopped by the user. * Show the title in the configured language when launching an application * Rename DesiredTitleLanguage to DesiredLanguage
This commit is contained in:
		@@ -256,6 +256,12 @@ namespace Ryujinx
 | 
			
		||||
            MainWindow mainWindow = new();
 | 
			
		||||
            mainWindow.Show();
 | 
			
		||||
 | 
			
		||||
            // Load the game table if no application was requested by the command line
 | 
			
		||||
            if (CommandLineState.LaunchPathArg == null)
 | 
			
		||||
            {
 | 
			
		||||
                mainWindow.UpdateGameTable();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (OperatingSystem.IsLinux())
 | 
			
		||||
            {
 | 
			
		||||
                int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
 | 
			
		||||
 
 | 
			
		||||
@@ -187,7 +187,10 @@ namespace Ryujinx.UI
 | 
			
		||||
                : IntegrityCheckLevel.None;
 | 
			
		||||
 | 
			
		||||
            // Instantiate GUI objects.
 | 
			
		||||
            ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
 | 
			
		||||
            ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
 | 
			
		||||
            {
 | 
			
		||||
                DesiredLanguage = ConfigurationState.Instance.System.Language,
 | 
			
		||||
            };
 | 
			
		||||
            _uiHandler = new GtkHostUIHandler(this);
 | 
			
		||||
            _deviceExitStatus = new AutoResetEvent(false);
 | 
			
		||||
 | 
			
		||||
@@ -325,7 +328,6 @@ namespace Ryujinx.UI
 | 
			
		||||
            _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
 | 
			
		||||
 | 
			
		||||
            UpdateColumns();
 | 
			
		||||
            UpdateGameTable();
 | 
			
		||||
 | 
			
		||||
            ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
 | 
			
		||||
            {
 | 
			
		||||
@@ -738,7 +740,8 @@ namespace Ryujinx.UI
 | 
			
		||||
 | 
			
		||||
            Thread applicationLibraryThread = new(() =>
 | 
			
		||||
            {
 | 
			
		||||
                ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
 | 
			
		||||
                ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
 | 
			
		||||
                ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
 | 
			
		||||
 | 
			
		||||
                _updatingGameTable = false;
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
{
 | 
			
		||||
    public class ApplicationLibrary
 | 
			
		||||
    {
 | 
			
		||||
        public Language DesiredLanguage { get; set; }
 | 
			
		||||
        public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
 | 
			
		||||
        public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
 | 
			
		||||
        private readonly VirtualFileSystem _virtualFileSystem;
 | 
			
		||||
        private readonly IntegrityCheckLevel _checkLevel;
 | 
			
		||||
        private Language _desiredTitleLanguage;
 | 
			
		||||
        private CancellationTokenSource _cancellationToken;
 | 
			
		||||
 | 
			
		||||
        private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 | 
			
		||||
@@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
                {
 | 
			
		||||
                    using UniqueRef<IFile> icon = new();
 | 
			
		||||
 | 
			
		||||
                    controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
 | 
			
		||||
                    controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
 | 
			
		||||
 | 
			
		||||
                    using MemoryStream stream = new();
 | 
			
		||||
 | 
			
		||||
@@ -432,35 +432,40 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
 | 
			
		||||
            foreach (var data in applications)
 | 
			
		||||
            {
 | 
			
		||||
                ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
 | 
			
		||||
                // Only load metadata for applications with an ID
 | 
			
		||||
                if (data.Id != 0)
 | 
			
		||||
                {
 | 
			
		||||
                    appMetadata.Title = data.Name;
 | 
			
		||||
 | 
			
		||||
                    // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
 | 
			
		||||
                    if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
 | 
			
		||||
                    ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
 | 
			
		||||
                    {
 | 
			
		||||
                        appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
 | 
			
		||||
                        appMetadata.TimePlayedOld = default;
 | 
			
		||||
                    }
 | 
			
		||||
                        appMetadata.Title = data.Name;
 | 
			
		||||
 | 
			
		||||
                    // Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
 | 
			
		||||
                    if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
 | 
			
		||||
                    {
 | 
			
		||||
                        // Migrate from string-based last_played to DateTime-based last_played_utc.
 | 
			
		||||
                        if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
 | 
			
		||||
                        // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
 | 
			
		||||
                        if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
 | 
			
		||||
                        {
 | 
			
		||||
                            appMetadata.LastPlayed = lastPlayedOldParsed;
 | 
			
		||||
 | 
			
		||||
                            // Migration successful: deleting last_played from the metadata file.
 | 
			
		||||
                            appMetadata.LastPlayedOld = default;
 | 
			
		||||
                            appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
 | 
			
		||||
                            appMetadata.TimePlayedOld = default;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                        // Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
 | 
			
		||||
                        if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
 | 
			
		||||
                        {
 | 
			
		||||
                            // Migrate from string-based last_played to DateTime-based last_played_utc.
 | 
			
		||||
                            if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
 | 
			
		||||
                            {
 | 
			
		||||
                                appMetadata.LastPlayed = lastPlayedOldParsed;
 | 
			
		||||
 | 
			
		||||
                                // Migration successful: deleting last_played from the metadata file.
 | 
			
		||||
                                appMetadata.LastPlayedOld = default;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    data.Favorite = appMetadata.Favorite;
 | 
			
		||||
                    data.TimePlayed = appMetadata.TimePlayed;
 | 
			
		||||
                    data.LastPlayed = appMetadata.LastPlayed;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                data.Favorite = appMetadata.Favorite;
 | 
			
		||||
                data.TimePlayed = appMetadata.TimePlayed;
 | 
			
		||||
                data.LastPlayed = appMetadata.LastPlayed;
 | 
			
		||||
                data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
 | 
			
		||||
                data.FileSize = fileSize;
 | 
			
		||||
                data.Path = applicationPath;
 | 
			
		||||
@@ -482,13 +487,11 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
            controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
 | 
			
		||||
        public void LoadApplications(List<string> appDirs)
 | 
			
		||||
        {
 | 
			
		||||
            int numApplicationsFound = 0;
 | 
			
		||||
            int numApplicationsLoaded = 0;
 | 
			
		||||
 | 
			
		||||
            _desiredTitleLanguage = desiredTitleLanguage;
 | 
			
		||||
 | 
			
		||||
            _cancellationToken = new CancellationTokenSource();
 | 
			
		||||
 | 
			
		||||
            // Builds the applications list with paths to found applications
 | 
			
		||||
@@ -847,7 +850,7 @@ namespace Ryujinx.UI.App.Common
 | 
			
		||||
 | 
			
		||||
        private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
 | 
			
		||||
        {
 | 
			
		||||
            _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
 | 
			
		||||
            _ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
 | 
			
		||||
 | 
			
		||||
            if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
        internal static MainWindowViewModel MainWindowViewModel { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private bool _isLoading;
 | 
			
		||||
        private bool _applicationsLoadedOnce;
 | 
			
		||||
 | 
			
		||||
        private UserChannelPersistence _userChannelPersistence;
 | 
			
		||||
        private static bool _deferLoad;
 | 
			
		||||
@@ -224,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
                ? IntegrityCheckLevel.ErrorOnInvalid
 | 
			
		||||
                : IntegrityCheckLevel.None;
 | 
			
		||||
 | 
			
		||||
            ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
 | 
			
		||||
            ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel)
 | 
			
		||||
            {
 | 
			
		||||
                DesiredLanguage = ConfigurationState.Instance.System.Language,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Save data created before we supported extra data in directory save data will not work properly if
 | 
			
		||||
            // given empty extra data. Luckily some of that extra data can be created using the data from the
 | 
			
		||||
@@ -472,7 +476,11 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
 | 
			
		||||
            ViewModel.RefreshFirmwareStatus();
 | 
			
		||||
 | 
			
		||||
            LoadApplications();
 | 
			
		||||
            // Load applications if no application was requested by the command line
 | 
			
		||||
            if (!_deferLoad)
 | 
			
		||||
            {
 | 
			
		||||
                LoadApplications();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
 | 
			
		||||
            CheckLaunchState();
 | 
			
		||||
@@ -485,6 +493,12 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
 | 
			
		||||
            if (MainContent.Content != content)
 | 
			
		||||
            {
 | 
			
		||||
                // Load applications while switching to the GameLibrary if we haven't done that yet
 | 
			
		||||
                if (!_applicationsLoadedOnce && content == GameLibrary)
 | 
			
		||||
                {
 | 
			
		||||
                    LoadApplications();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                MainContent.Content = content;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -581,6 +595,7 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
 | 
			
		||||
        public void LoadApplications()
 | 
			
		||||
        {
 | 
			
		||||
            _applicationsLoadedOnce = true;
 | 
			
		||||
            ViewModel.Applications.Clear();
 | 
			
		||||
 | 
			
		||||
            StatusBarView.LoadProgressBar.IsVisible = true;
 | 
			
		||||
@@ -622,7 +637,8 @@ namespace Ryujinx.Ava.UI.Windows
 | 
			
		||||
 | 
			
		||||
            Thread applicationLibraryThread = new(() =>
 | 
			
		||||
            {
 | 
			
		||||
                ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
 | 
			
		||||
                ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
 | 
			
		||||
                ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
 | 
			
		||||
 | 
			
		||||
                _isLoading = false;
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user