aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2023-11-11 23:35:30 -0300
committerGitHub <noreply@github.com>2023-11-11 23:35:30 -0300
commit51065d91290e41a9d2518f44c9bdf83a9b0017ab (patch)
tree4964520c8d5dbb1000b8eec4a024744df1a3e4ee
parent6228331fd1fb63a32d929bf1cae7f709bc9fd271 (diff)
Revert "Add support for multi game XCIs (#5638)" (#5914)1.1.1079
This reverts commit 5c3cfb84c09b0566da677425915afa0b2d76da55.
-rw-r--r--src/Ryujinx.Ava/AppHost.cs9
-rw-r--r--src/Ryujinx.Ava/Assets/Locales/en_US.json2
-rw-r--r--src/Ryujinx.Ava/Common/ApplicationHelper.cs9
-rw-r--r--src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs54
-rw-r--r--src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml2
-rw-r--r--src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml4
-rw-r--r--src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs6
-rw-r--r--src/Ryujinx.Ava/UI/Models/SaveModel.cs4
-rw-r--r--src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs5
-rw-r--r--src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs60
-rw-r--r--src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs47
-rw-r--r--src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs56
-rw-r--r--src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs10
-rw-r--r--src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml2
-rw-r--r--src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs7
-rw-r--r--src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml2
-rw-r--r--src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs12
-rw-r--r--src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs19
-rw-r--r--src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs14
-rw-r--r--src/Ryujinx.HLE/FileSystem/ContentCollection.cs61
-rw-r--r--src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs100
-rw-r--r--src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs153
-rw-r--r--src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs8
-rw-r--r--src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs9
-rw-r--r--src/Ryujinx.HLE/Switch.cs8
-rw-r--r--src/Ryujinx.Ui.Common/App/ApplicationData.cs18
-rw-r--r--src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs812
-rw-r--r--src/Ryujinx/Program.cs8
-rw-r--r--src/Ryujinx/Ui/MainWindow.cs95
-rw-r--r--src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs89
-rw-r--r--src/Ryujinx/Ui/Windows/CheatWindow.cs9
-rw-r--r--src/Ryujinx/Ui/Windows/DlcWindow.cs123
-rw-r--r--src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs63
33 files changed, 764 insertions, 1116 deletions
diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs
index 053d5b52..4d751e2a 100644
--- a/src/Ryujinx.Ava/AppHost.cs
+++ b/src/Ryujinx.Ava/AppHost.cs
@@ -54,6 +54,8 @@ using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;
+using InputManager = Ryujinx.Input.HLE.InputManager;
+using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key;
using MouseButton = Ryujinx.Input.MouseButton;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
@@ -121,14 +123,12 @@ namespace Ryujinx.Ava
public int Width { get; private set; }
public int Height { get; private set; }
public string ApplicationPath { get; private set; }
- public ulong ApplicationId { get; private set; }
public bool ScreenshotRequested { get; set; }
public AppHost(
RendererHost renderer,
InputManager inputManager,
string applicationPath,
- ulong applicationId,
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
AccountManager accountManager,
@@ -152,7 +152,6 @@ namespace Ryujinx.Ava
NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager();
ApplicationPath = applicationPath;
- ApplicationId = applicationId;
VirtualFileSystem = virtualFileSystem;
ContentManager = contentManager;
@@ -642,7 +641,7 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
- if (!Device.LoadXci(ApplicationPath, ApplicationId))
+ if (!Device.LoadXci(ApplicationPath))
{
Device.Dispose();
@@ -669,7 +668,7 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
- if (!Device.LoadNsp(ApplicationPath, ApplicationId))
+ if (!Device.LoadNsp(ApplicationPath))
{
Device.Dispose();
diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json
index be3e35a9..bc2bbfe8 100644
--- a/src/Ryujinx.Ava/Assets/Locales/en_US.json
+++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json
@@ -539,8 +539,6 @@
"OpenSetupGuideMessage": "Open the Setup Guide",
"NoUpdate": "No Update",
"TitleUpdateVersionLabel": "Version {0}",
- "TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
- "TitleBundledDlcLabel": "Bundled:",
"RyujinxInfo": "Ryujinx - Info",
"RyujinxConfirm": "Ryujinx - Confirmation",
"FileDialogAllTypes": "All types",
diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx.Ava/Common/ApplicationHelper.cs
index dd464329..91ca8f4d 100644
--- a/src/Ryujinx.Ava/Common/ApplicationHelper.cs
+++ b/src/Ryujinx.Ava/Common/ApplicationHelper.cs
@@ -18,8 +18,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
-using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Buffers;
@@ -227,11 +226,7 @@ namespace Ryujinx.Ava.Common
return;
}
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
- (Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
+ (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
{
patchNca = updatePatchNca;
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
index 69465c7c..0f007106 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
@@ -14,6 +15,7 @@ using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using Path = System.IO.Path;
@@ -39,7 +41,7 @@ namespace Ryujinx.Ava.UI.Controls
{
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
- ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
+ ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
@@ -74,9 +76,19 @@ namespace Ryujinx.Ava.UI.Controls
{
if (viewModel?.SelectedApplication != null)
{
- var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
+ if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
+ });
+
+ return;
+ }
+
+ var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
- ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
+ ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
}
}
@@ -86,7 +98,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
- await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
+ await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
}
}
@@ -96,7 +108,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
- await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
+ await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
}
}
@@ -108,8 +120,8 @@ namespace Ryujinx.Ava.UI.Controls
{
await new CheatWindow(
viewModel.VirtualFileSystem,
- viewModel.SelectedApplication.IdString,
- viewModel.SelectedApplication.Name,
+ viewModel.SelectedApplication.TitleId,
+ viewModel.SelectedApplication.TitleName,
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
}
}
@@ -121,7 +133,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
string modsBasePath = ModLoader.GetModsBasePath();
- string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.IdString);
+ string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -134,7 +146,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
- string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
+ string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -148,15 +160,15 @@ namespace Ryujinx.Ava.UI.Controls
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
- LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
- DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
- DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
+ DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
List<FileInfo> cacheFiles = new();
@@ -196,14 +208,14 @@ namespace Ryujinx.Ava.UI.Controls
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
- LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
- DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
+ DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"));
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
@@ -251,7 +263,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
- string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
+ string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu");
string mainDir = Path.Combine(ptcDir, "0");
string backupDir = Path.Combine(ptcDir, "1");
@@ -272,7 +284,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
- string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
+ string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
@@ -293,7 +305,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Code,
viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
+ viewModel.SelectedApplication.TitleName);
}
}
@@ -307,7 +319,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Data,
viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
+ viewModel.SelectedApplication.TitleName);
}
}
@@ -321,7 +333,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Logo,
viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
+ viewModel.SelectedApplication.TitleName);
}
}
@@ -332,7 +344,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
ApplicationData selectedApplication = viewModel.SelectedApplication;
- ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon);
+ ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
}
}
@@ -342,7 +354,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
- await viewModel.LoadApplication(viewModel.SelectedApplication);
+ await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
}
}
}
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml
index 5919652e..bbdb4c4a 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml
@@ -82,7 +82,7 @@
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
- Text="{Binding Name}"
+ Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Panel>
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
index 24ec2b35..9004f751 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
@@ -85,7 +85,7 @@
<TextBlock
HorizontalAlignment="Stretch"
FontWeight="Bold"
- Text="{Binding Name}"
+ Text="{Binding TitleName}"
TextAlignment="Left"
TextWrapping="Wrap" />
<TextBlock
@@ -109,7 +109,7 @@
Spacing="5">
<TextBlock
HorizontalAlignment="Stretch"
- Text="{Binding Id, StringFormat=X16}"
+ Text="{Binding TitleId}"
TextAlignment="Left"
TextWrapping="Wrap" />
<TextBlock
diff --git a/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs b/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs
index e39ffead..fedb3527 100644
--- a/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs
+++ b/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs
@@ -1,5 +1,4 @@
-using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.ViewModels;
using System.IO;
namespace Ryujinx.Ava.UI.Models
@@ -25,9 +24,6 @@ namespace Ryujinx.Ava.UI.Models
public string FileName => Path.GetFileName(ContainerPath);
- public string Label =>
- Path.GetExtension(FileName)?.ToLower() == ".xci" ? $"{LocaleManager.Instance[LocaleKeys.TitleBundledDlcLabel]} {FileName}" : FileName;
-
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
{
TitleId = titleId;
diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
index 2e3ed3ba..7b476932 100644
--- a/src/Ryujinx.Ava/UI/Models/SaveModel.cs
+++ b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
@@ -46,14 +46,14 @@ namespace Ryujinx.Ava.UI.Models
TitleId = info.ProgramId;
UserId = info.UserId;
- var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString);
+ var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
InGameList = appData != null;
if (InGameList)
{
Icon = appData.Icon;
- Title = appData.Name;
+ Title = appData.TitleName;
}
else
{
diff --git a/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs b/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs
index fae2a08d..3b44e8ee 100644
--- a/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs
+++ b/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs
@@ -8,10 +8,7 @@ namespace Ryujinx.Ava.UI.Models
public ApplicationControlProperty Control { get; }
public string Path { get; }
- public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(
- System.IO.Path.GetExtension(Path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel,
- Control.DisplayVersionString.ToString()
- );
+ public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
public TitleUpdateModel(ApplicationControlProperty control, string path)
{
diff --git a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
index 9f3a0045..cdecae77 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
@@ -17,12 +17,11 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
-using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using Application = Avalonia.Application;
using Path = System.IO.Path;
@@ -39,7 +38,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private string _search;
- private readonly ApplicationData _applicationData;
+ private readonly ulong _titleId;
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -93,25 +92,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public IStorageProvider StorageProvider;
- public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
_virtualFileSystem = virtualFileSystem;
- _applicationData = applicationData;
+ _titleId = titleId;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
StorageProvider = desktop.MainWindow.StorageProvider;
}
- _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json");
-
- if (!File.Exists(_downloadableContentJsonPath))
- {
- _downloadableContentContainerList = new List<DownloadableContentContainer>();
-
- Save();
- }
+ _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
@@ -128,9 +120,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadDownloadableContents()
{
- // NOTE: Try to load downloadable contents from PFS first.
- AddDownloadableContent(_applicationData.Path);
-
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
{
if (File.Exists(downloadableContentContainer.ContainerPath))
@@ -138,11 +127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new();
-
- if (partitionFileSystem.Initialize(containerFile.AsStorage()).IsFailure())
- {
- continue;
- }
+ partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem);
@@ -235,34 +220,22 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var file in result)
{
- if (!AddDownloadableContent(file.Path.LocalPath))
- {
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
- }
+ await AddDownloadableContent(file.Path.LocalPath);
}
}
- private bool AddDownloadableContent(string path)
+ private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
- return true;
+ return;
}
using FileStream containerFile = File.OpenRead(path);
- IFileSystem partitionFileSystem;
-
- if (Path.GetExtension(path).ToLower() == ".xci")
- {
- partitionFileSystem = new Xci(_virtualFileSystem.KeySet, containerFile.AsStorage()).OpenPartition(XciPartitionType.Secure);
- }
- else
- {
- var pfsTemp = new PartitionFileSystem();
- pfsTemp.Initialize(containerFile.AsStorage()).ThrowIfFailure();
- partitionFileSystem = pfsTemp;
- }
+ PartitionFileSystem partitionFileSystem = new();
+ partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
+ bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
@@ -280,7 +253,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (nca.Header.ContentType == NcaContentType.PublicData)
{
- if (nca.GetProgramIdBase() != _applicationData.IdBase)
+ if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
@@ -292,11 +265,14 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged(nameof(UpdateCount));
Sort();
- return true;
+ containsDownloadableContent = true;
}
}
- return false;
+ if (!containsDownloadableContent)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
+ }
}
public void Remove(DownloadableContentModel model)
diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index 692df483..80df5d39 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _canUpdate = true;
private Cursor _cursor;
private string _title;
- private ApplicationData _currentApplicationData;
+ private string _currentEmulatedGamePath;
private readonly AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
@@ -106,6 +106,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
+ private string TitleName { get; set; }
internal AppHost AppHost { get; set; }
public MainWindowViewModel()
@@ -929,8 +930,8 @@ namespace Ryujinx.Ava.UI.ViewModels
return SortMode switch
{
#pragma warning disable IDE0055 // Disable formatting
- ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name)
- : SortExpressionComparer<ApplicationData>.Descending(app => app.Name),
+ ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
+ : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
@@ -967,7 +968,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (arg is ApplicationData app)
{
- return string.IsNullOrWhiteSpace(_searchText) || app.Name.ToLower().Contains(_searchText.ToLower());
+ return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower());
}
return false;
@@ -1096,7 +1097,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false;
break;
case LoadState.Loaded:
- LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
+ LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
@@ -1116,7 +1117,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
- LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
+ LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
@@ -1167,13 +1168,13 @@ namespace Ryujinx.Ava.UI.ViewModels
{
UserChannelPersistence.ShouldRestart = false;
- await LoadApplication(_currentApplicationData);
+ await LoadApplication(_currentEmulatedGamePath);
}
else
{
// Otherwise, clear state.
UserChannelPersistence = new UserChannelPersistence();
- _currentApplicationData = null;
+ _currentEmulatedGamePath = null;
}
}
@@ -1450,12 +1451,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result.Count > 0)
{
- ApplicationData applicationData = new()
- {
- Path = result[0].Path.LocalPath,
- };
-
- await LoadApplication(applicationData);
+ await LoadApplication(result[0].Path.LocalPath);
}
}
@@ -1469,17 +1465,11 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result.Count > 0)
{
- ApplicationData applicationData = new()
- {
- Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
- Path = result[0].Path.LocalPath,
- };
-
- await LoadApplication(applicationData);
+ await LoadApplication(result[0].Path.LocalPath);
}
}
- public async Task LoadApplication(ApplicationData application, bool startFullscreen = false)
+ public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
{
if (AppHost != null)
{
@@ -1499,7 +1489,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
- SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
+ SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
PrepareLoadScreen();
@@ -1508,8 +1498,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost = new AppHost(
RendererHostControl,
InputManager,
- application.Path,
- application.Id,
+ path,
VirtualFileSystem,
ContentManager,
AccountManager,
@@ -1527,17 +1516,17 @@ namespace Ryujinx.Ava.UI.ViewModels
CanUpdate = false;
- LoadHeading = application.Name;
+ LoadHeading = TitleName = titleName;
- if (string.IsNullOrWhiteSpace(application.Name))
+ if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
- application.Name = AppHost.Device.Processes.ActiveApplication.Name;
+ TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
- _currentApplicationData = application;
+ _currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
index 7bb96131..5090a8c7 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
@@ -1,3 +1,4 @@
+using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
@@ -7,7 +8,6 @@ using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ns;
-using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
@@ -17,16 +17,12 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
-using Application = Avalonia.Application;
-using ContentType = LibHac.Ncm.ContentType;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
@@ -37,7 +33,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public TitleUpdateMetadata TitleUpdateWindowData;
public readonly string TitleUpdateJsonPath;
private VirtualFileSystem VirtualFileSystem { get; }
- private ApplicationData ApplicationData { get; }
+ private ulong TitleId { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
@@ -77,18 +73,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public IStorageProvider StorageProvider;
- public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
VirtualFileSystem = virtualFileSystem;
- ApplicationData = applicationData;
+ TitleId = titleId;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
StorageProvider = desktop.MainWindow.StorageProvider;
}
- TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdString, "updates.json");
+ TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
@@ -96,7 +92,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
catch
{
- Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdString} at {TitleUpdateJsonPath}");
+ Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
TitleUpdateWindowData = new TitleUpdateMetadata
{
@@ -112,9 +108,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadUpdates()
{
- // Try to load updates from PFS first
- AddUpdate(ApplicationData.Path, true);
-
foreach (string path in TitleUpdateWindowData.Paths)
{
AddUpdate(path);
@@ -169,41 +162,17 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- private void AddUpdate(string path, bool ignoreNotFound = false)
+ private void AddUpdate(string path)
{
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
{
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
- IFileSystem pfs;
-
try
{
- if (Path.GetExtension(path).ToLower() == ".xci")
- {
- pfs = new Xci(VirtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
- }
- else
- {
- var pfsTemp = new PartitionFileSystem();
- pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
- pfs = pfsTemp;
- }
-
- Dictionary<ulong, ContentCollection> updates = pfs.GetUpdateData(VirtualFileSystem, checkLevel);
-
- Nca patchNca = null;
- Nca controlNca = null;
-
- if (updates.TryGetValue(ApplicationData.Id, out ContentCollection content))
- {
- patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
- controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
- }
+ var pfs = new PartitionFileSystem();
+ pfs.Initialize(file.AsStorage()).ThrowIfFailure();
+ (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
@@ -218,10 +187,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
else
{
- if (!ignoreNotFound)
- {
- Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
- }
+ Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
}
}
catch (Exception ex)
diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
index af3c5deb..4f2d262d 100644
--- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
@@ -10,7 +10,6 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Modules;
-using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@@ -132,14 +131,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (!string.IsNullOrEmpty(contentPath))
{
- ApplicationData applicationData = new()
- {
- Name = "miiEdit",
- Id = 0x0100000000001009,
- Path = contentPath,
- };
-
- await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen);
+ await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
}
}
diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml
index 7a716fb2..cc21b5c6 100644
--- a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml
+++ b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml
@@ -104,7 +104,7 @@
Content="{locale:Locale GameListHeaderApplication}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
- Tag="Application" />
+ Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderDeveloper}"
diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
index 76f1a991..fde249a0 100644
--- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
@@ -1,11 +1,9 @@
using Avalonia.Collections;
-using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -36,12 +34,9 @@ namespace Ryujinx.Ava.UI.Windows
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
{
LoadedCheats = new AvaloniaList<CheatsList>();
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
- BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath);
+ BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
InitializeComponent();
diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml
index 98aac09c..99cf28e7 100644
--- a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml
@@ -97,7 +97,7 @@
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
- Text="{Binding Label}" />
+ Text="{Binding FileName}" />
<TextBlock
Grid.Column="1"
Margin="10 0"
diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs
index c871ae19..dfe8807b 100644
--- a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs
@@ -7,9 +7,9 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using System.Threading.Tasks;
+using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
@@ -24,22 +24,22 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
}
- public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
{
- DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData);
+ DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId);
InitializeComponent();
}
- public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
- Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
- Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdString),
+ Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
+ Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
};
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
index 352ac4e5..c78f4160 100644
--- a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
@@ -4,7 +4,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
-using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
@@ -24,6 +23,7 @@ using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
+using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -139,7 +139,9 @@ namespace Ryujinx.Ava.UI.Windows
{
ViewModel.SelectedIcon = args.Application.Icon;
- ViewModel.LoadApplication(args.Application).Wait();
+ string path = new FileInfo(args.Application.Path).FullName;
+
+ ViewModel.LoadApplication(path).Wait();
}
args.Handled = true;
@@ -188,11 +190,7 @@ namespace Ryujinx.Ava.UI.Windows
LibHacHorizonManager.InitializeBcatServer();
LibHacHorizonManager.InitializeSystemClients();
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
- ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
+ ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
// 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
@@ -299,12 +297,7 @@ namespace Ryujinx.Ava.UI.Windows
{
_deferLoad = false;
- ApplicationData applicationData = new()
- {
- Path = _launchPath,
- };
-
- ViewModel.LoadApplication(applicationData, _startFullscreen).Wait();
+ ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
}
}
else
diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
index 8ecf165c..7ece6335 100644
--- a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
@@ -7,15 +7,15 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using System.Threading.Tasks;
+using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
public partial class TitleUpdateWindow : UserControl
{
- public readonly TitleUpdateViewModel ViewModel;
+ public TitleUpdateViewModel ViewModel;
public TitleUpdateWindow()
{
@@ -24,22 +24,22 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
}
- public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
{
- DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData);
+ DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId);
InitializeComponent();
}
- public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
+ public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
- Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
- Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdString),
+ Content = new TitleUpdateWindow(virtualFileSystem, titleId),
+ Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
};
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
diff --git a/src/Ryujinx.HLE/FileSystem/ContentCollection.cs b/src/Ryujinx.HLE/FileSystem/ContentCollection.cs
deleted file mode 100644
index 1c19887b..00000000
--- a/src/Ryujinx.HLE/FileSystem/ContentCollection.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using LibHac.Common.Keys;
-using LibHac.Fs.Fsa;
-using LibHac.Ncm;
-using LibHac.Tools.FsSystem.NcaUtils;
-using LibHac.Tools.Ncm;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
-using System;
-
-namespace Ryujinx.HLE.FileSystem
-{
- /// <summary>
- /// Thin wrapper around <see cref="Cnmt"/>
- /// </summary>
- public class ContentCollection
- {
- private readonly IFileSystem _pfs;
- private readonly Cnmt _cnmt;
-
- public ulong Id => _cnmt.TitleId;
- public TitleVersion Version => _cnmt.TitleVersion;
- public ContentMetaType Type => _cnmt.Type;
- public ulong ApplicationId => _cnmt.ApplicationTitleId;
- public ulong PatchId => _cnmt.PatchTitleId;
- public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion;
- public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion;
- public byte[] Digest => _cnmt.Hash;
-
- public ulong ProgramBaseId => Id & ~0x1FFFUL;
- public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application;
-
- public ContentCollection(IFileSystem pfs, Cnmt cnmt)
- {
- _pfs = pfs;
- _cnmt = cnmt;
- }
-
- public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0)
- {
- // TODO: Replace this with a check for IdOffset as soon as LibHac supports it:
- // && entry.IdOffset == programIndex
-
- foreach (var entry in _cnmt.ContentEntries)
- {
- if (entry.Type != type)
- {
- continue;
- }
-
- string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower();
- Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca");
-
- if (nca.GetProgramIndex() == programIndex)
- {
- return nca;
- }
- }
-
- return null;
- }
- }
-}
diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
index 6863d1a7..4568b44d 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
@@ -2,31 +2,21 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
-using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Ns;
-using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
-using LibHac.Tools.Ncm;
-using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
-using Ryujinx.Common.Utilities;
-using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using System.IO;
using System.Linq;
using ApplicationId = LibHac.Ncm.ApplicationId;
-using ContentType = LibHac.Ncm.ContentType;
-using Path = System.IO.Path;
namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
- public static class NcaExtensions
+ static class NcaExtensions
{
- private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
-
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
{
// Extract RomFs and ExeFs from NCA.
@@ -57,7 +47,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
nacpData = controlNca.GetNacp(device);
}
- /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
+ /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
// Load program 0 control NCA as we are going to need it for display version.
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
@@ -96,11 +86,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return processResult;
}
- public static ulong GetProgramIdBase(this Nca nca)
- {
- return nca.Header.TitleId & ~0x1FFFUL;
- }
-
public static int GetProgramIndex(this Nca nca)
{
return (int)(nca.Header.TitleId & 0xF);
@@ -111,11 +96,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nca.Header.ContentType == NcaContentType.Program;
}
- public static bool IsMain(this Nca nca)
- {
- return nca.IsProgram() && !nca.IsPatch();
- }
-
public static bool IsPatch(this Nca nca)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
@@ -128,56 +108,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nca.Header.ContentType == NcaContentType.Control;
}
- public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath)
- {
- updatePath = "(unknown)";
-
- // Load Update NCAs.
- Nca updatePatchNca = null;
- Nca updateControlNca = null;
-
- // Clear the program index part.
- ulong titleIdBase = mainNca.GetProgramIdBase();
-
- // Load update information if exists.
- string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
- if (File.Exists(titleUpdateMetadataPath))
- {
- updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
- if (File.Exists(updatePath))
- {
- var updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
-
- IFileSystem updatePartitionFileSystem;
-
- if (Path.GetExtension(updatePath).ToLower() == ".xci")
- {
- updatePartitionFileSystem = new Xci(fileSystem.KeySet, updateFile.AsStorage()).OpenPartition(XciPartitionType.Secure);
- }
- else
- {
- PartitionFileSystem pfsTemp = new();
- pfsTemp.Initialize(updateFile.AsStorage()).ThrowIfFailure();
- updatePartitionFileSystem = pfsTemp;
- }
-
- foreach ((ulong updateTitleId, ContentCollection content) in updatePartitionFileSystem.GetUpdateData(fileSystem, checkLevel))
- {
- if ((updateTitleId & ~0x1FFFUL) != titleIdBase)
- {
- continue;
- }
-
- updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex);
- updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex);
- break;
- }
- }
- }
-
- return (updatePatchNca, updateControlNca);
- }
-
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
{
IFileSystem exeFs = null;
@@ -242,31 +172,5 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nacpData;
}
-
- public static Cnmt GetCnmt(this Nca cnmtNca, IntegrityCheckLevel checkLevel, ContentMetaType metaType)
- {
- string path = $"/{metaType}_{cnmtNca.Header.TitleId:x16}.cnmt";
- using var cnmtFile = new UniqueRef<IFile>();
-
- try
- {
- Result result = cnmtNca.OpenFileSystem(0, checkLevel)
- .OpenFile(ref cnmtFile.Ref, path.ToU8Span(), OpenMode.Read);
-
- if (result.IsSuccess())
- {
- return new Cnmt(cnmtFile.Release().AsStream());
- }
- }
- catch (HorizonResultException ex)
- {
- if (!ResultFs.PathNotFound.Includes(ex.ResultValue))
- {
- Logger.Warning?.Print(LogClass.Application, $"Failed get cnmt for '{cnmtNca.Header.TitleId:x16}' from nca: {ex.Message}");
- }
- }
-
- return null;
- }
}
}
diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
index 5f45cd45..50f7d585 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
@@ -1,87 +1,26 @@
using LibHac.Common;
-using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
-using LibHac.Ncm;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
-using LibHac.Tools.Ncm;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
-using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
-using ContentType = LibHac.Ncm.ContentType;
namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
public static class PartitionFileSystemExtensions
{
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+ private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
- public static Dictionary<ulong, ContentCollection> GetApplicationData(this IFileSystem partitionFileSystem,
- VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel)
- {
- fileSystem.ImportTickets(partitionFileSystem);
-
- var programs = new Dictionary<ulong, ContentCollection>();
-
- foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca"))
- {
- Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, ContentMetaType.Application);
-
- if (cnmt == null)
- {
- continue;
- }
-
- ContentCollection content = new(partitionFileSystem, cnmt);
-
- if (content.Type != ContentMetaType.Application)
- {
- continue;
- }
-
- programs.TryAdd(content.ApplicationId, content);
- }
-
- return programs;
- }
-
- public static Dictionary<ulong, ContentCollection> GetUpdateData(this IFileSystem partitionFileSystem,
- VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel)
- {
- fileSystem.ImportTickets(partitionFileSystem);
-
- var programs = new Dictionary<ulong, ContentCollection>();
-
- foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca"))
- {
- Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, ContentMetaType.Patch);
-
- if (cnmt == null)
- {
- continue;
- }
-
- ContentCollection content = new(partitionFileSystem, cnmt);
-
- if (content.Type != ContentMetaType.Patch)
- {
- continue;
- }
-
- programs.TryAdd(content.ApplicationId, content);
- }
-
- return programs;
- }
-
- internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong titleId, out string errorMessage)
+ internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
where TFormat : IPartitionFileSystemFormat
where THeader : unmanaged, IPartitionFileSystemHeader
@@ -96,21 +35,30 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
try
{
- Dictionary<ulong, ContentCollection> applications = partitionFileSystem.GetApplicationData(device.FileSystem, device.System.FsIntegrityCheckLevel);
+ device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
- if (titleId == 0)
+ // TODO: To support multi-games container, this should use CNMT NCA instead.
+ foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
- foreach ((ulong _, ContentCollection content) in applications)
+ Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
+
+ if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
{
- mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
- controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
- break;
+ continue;
+ }
+
+ if (nca.IsPatch())
+ {
+ patchNca = nca;
+ }
+ else if (nca.IsProgram())
+ {
+ mainNca = nca;
+ }
+ else if (nca.IsControl())
+ {
+ controlNca = nca;
}
- }
- else if (applications.TryGetValue(titleId, out ContentCollection content))
- {
- mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
- controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
}
ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
@@ -131,7 +79,54 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (false, ProcessResult.Failed);
}
- (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
+ // Load Update NCAs.
+ Nca updatePatchNca = null;
+ Nca updateControlNca = null;
+
+ if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+ {
+ // Clear the program index part.
+ titleIdBase &= ~0xFUL;
+
+ // Load update information if exists.
+ string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+ if (File.Exists(titleUpdateMetadataPath))
+ {
+ string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
+ if (File.Exists(updatePath))
+ {
+ PartitionFileSystem updatePartitionFileSystem = new();
+ updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure();
+
+ device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
+
+ // TODO: This should use CNMT NCA instead.
+ foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
+ {
+ Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
+
+ if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
+ {
+ continue;
+ }
+
+ if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
+ {
+ break;
+ }
+
+ if (nca.IsProgram())
+ {
+ updatePatchNca = nca;
+ }
+ else if (nca.IsControl())
+ {
+ updateControlNca = nca;
+ }
+ }
+ }
+ }
+ }
if (updatePatchNca != null)
{
@@ -173,18 +168,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (true, mainNca.Load(device, patchNca, controlNca));
}
- errorMessage = $"Unable to load: Could not find Main NCA for title \"{titleId:X16}\"";
+ errorMessage = "Unable to load: Could not find Main NCA";
return (false, ProcessResult.Failed);
}
- public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path)
+ public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
{
using var ncaFile = new UniqueRef<IFile>();
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- return new Nca(keySet, ncaFile.Release().AsStorage());
+ return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
}
}
}
diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
index 6b4a64be..220b868d 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
@@ -32,7 +32,7 @@ namespace Ryujinx.HLE.Loaders.Processes
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
}
- public bool LoadXci(string path, ulong titleId)
+ public bool LoadXci(string path)
{
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
@@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
- (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, titleId, out string errorMessage);
+ (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
if (!success)
{
@@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
- public bool LoadNsp(string path, ulong titleId)
+ public bool LoadNsp(string path)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
- (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, titleId, out string errorMessage);
+ (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
if (processResult.ProcessId == 0)
{
diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
index 110bb092..c229b174 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
@@ -42,14 +42,15 @@ namespace Ryujinx.HLE.Loaders.Processes
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
- Nca nca = partitionFileSystem.GetNca(device.FileSystem.KeySet, fileEntry.FullPath);
+ Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
- if (!nca.IsProgram())
+ if (!nca.IsProgram() && nca.IsPatch())
{
continue;
}
- ulong currentMainProgramId = nca.GetProgramIdBase();
+ ulong currentProgramId = nca.Header.TitleId;
+ ulong currentMainProgramId = currentProgramId & ~0xFFFul;
if (applicationId == 0 && currentMainProgramId != 0)
{
@@ -66,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
break;
}
- hasIndex[nca.GetProgramIndex()] = true;
+ hasIndex[(int)(currentProgramId & 0xF)] = true;
}
if (programCount == 0)
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index 3516049c..ae063a47 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -72,9 +72,9 @@ namespace Ryujinx.HLE
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
}
- public bool LoadXci(string xciFile, ulong titleId = 0)
+ public bool LoadXci(string xciFile)
{
- return Processes.LoadXci(xciFile, titleId);
+ return Processes.LoadXci(xciFile);
}
public bool LoadNca(string ncaFile)
@@ -82,9 +82,9 @@ namespace Ryujinx.HLE
return Processes.LoadNca(ncaFile);
}
- public bool LoadNsp(string nspFile, ulong titleId = 0)
+ public bool LoadNsp(string nspFile)
{
- return Processes.LoadNsp(nspFile, titleId);
+ return Processes.LoadNsp(nspFile);
}
public bool LoadProgram(string fileName)
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationData.cs b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
index 7495ccb5..65ab01ee 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationData.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
@@ -9,11 +9,9 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Ui.Common.Helper;
using System;
using System.IO;
-using System.Text.Json.Serialization;
namespace Ryujinx.Ui.App.Common
{
@@ -21,10 +19,10 @@ namespace Ryujinx.Ui.App.Common
{
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
- public string Name { get; set; } = "Unknown";
- public ulong Id { get; set; }
- public string Developer { get; set; } = "Unknown";
- public string Version { get; set; } = "0";
+ public string TitleName { get; set; }
+ public string TitleId { get; set; }
+ public string Developer { get; set; }
+ public string Version { get; set; }
public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }
public string FileExtension { get; set; }
@@ -38,11 +36,7 @@ namespace Ryujinx.Ui.App.Common
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
- [JsonIgnore] public string IdString => Id.ToString("x16");
-
- [JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL;
-
- public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
+ public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
{
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
@@ -111,7 +105,7 @@ namespace Ryujinx.Ui.App.Common
return string.Empty;
}
- (Nca updatePatchNca, _) = mainNca.GetUpdateData(virtualFileSystem, checkLevel, 0, out string _);
+ (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
if (updatePatchNca != null)
{
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
index 97612971..46f29851 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
@@ -14,18 +14,17 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Npdm;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
-using ContentType = LibHac.Ncm.ContentType;
using Path = System.IO.Path;
using TimeSpan = System.TimeSpan;
@@ -43,16 +42,15 @@ namespace Ryujinx.Ui.App.Common
private readonly byte[] _nsoIcon;
private readonly VirtualFileSystem _virtualFileSystem;
- private readonly IntegrityCheckLevel _checkLevel;
private Language _desiredTitleLanguage;
private CancellationTokenSource _cancellationToken;
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+ private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
- public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
+ public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
{
_virtualFileSystem = virtualFileSystem;
- _checkLevel = checkLevel;
_nspIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSP.png");
_xciIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_XCI.png");
@@ -71,241 +69,258 @@ namespace Ryujinx.Ui.App.Common
return resourceByteArray;
}
- private ApplicationData GetApplicationFromExeFs(PartitionFileSystem pfs, string filePath)
+ public void CancelLoading()
{
- ApplicationData data = new()
- {
- Icon = _nspIcon,
- };
-
- using UniqueRef<IFile> npdmFile = new();
-
- try
- {
- Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
-
- if (ResultFs.PathNotFound.Includes(result))
- {
- Npdm npdm = new(npdmFile.Get.AsStream());
-
- data.Name = npdm.TitleName;
- data.Id = npdm.Aci0.TitleId;
- }
+ _cancellationToken?.Cancel();
+ }
- return data;
- }
- catch (Exception exception)
- {
- Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception.Message}");
+ public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
+ {
+ using UniqueRef<IFile> controlFile = new();
- return null;
- }
+ controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
}
- private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string filePath)
+ public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
{
- bool isExeFs = false;
+ int numApplicationsFound = 0;
+ int numApplicationsLoaded = 0;
+
+ _desiredTitleLanguage = desiredTitleLanguage;
+
+ _cancellationToken = new CancellationTokenSource();
- // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
- bool hasMainNca = false;
+ // Builds the applications list with paths to found applications
+ List<string> applications = new();
- foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
+ try
{
- if (Path.GetExtension(fileEntry.FullPath)?.ToLower() == ".nca")
+ foreach (string appDir in appDirs)
{
- using UniqueRef<IFile> ncaFile = new();
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
- pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ if (!Directory.Exists(appDir))
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
- Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
- int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+ continue;
+ }
- // Some main NCAs don't have a data partition, so check if the partition exists before opening it
- if (nca.Header.ContentType == NcaContentType.Program &&
- !(nca.SectionExists(NcaSectionType.Data) &&
- nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
+ try
{
- hasMainNca = true;
+ IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", SearchOption.AllDirectories).Where(file =>
+ {
+ return
+ (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value) ||
+ (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value) ||
+ (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value) ||
+ (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value);
+ });
- break;
+ foreach (string app in files)
+ {
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var fileInfo = new FileInfo(app);
+ string extension = fileInfo.Extension.ToLower();
+
+ if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
+ {
+ var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
+ applications.Add(fullPath);
+ numApplicationsFound++;
+ }
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Failed to get access to directory: \"{appDir}\"");
}
}
- else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
- {
- isExeFs = true;
- }
- }
- if (hasMainNca)
- {
- List<ApplicationData> applications = GetApplicationsFromPfs(pfs, filePath);
-
- switch (applications.Count)
+ // Loops through applications list, creating a struct and then firing an event containing the struct for each application
+ foreach (string applicationPath in applications)
{
- case 1:
- return applications[0];
- case >= 1:
- Logger.Warning?.Print(LogClass.Application, $"File '{filePath}' contains more applications than expected: {applications.Count}");
- return applications[0];
- default:
- return null;
- }
- }
+ if (_cancellationToken.Token.IsCancellationRequested)
+ {
+ return;
+ }
- if (isExeFs)
- {
- return GetApplicationFromExeFs(pfs, filePath);
- }
+ long fileSize = new FileInfo(applicationPath).Length;
+ string titleName = "Unknown";
+ string titleId = "0000000000000000";
+ string developer = "Unknown";
+ string version = "0";
+ byte[] applicationIcon = null;
- return null;
- }
+ BlitStruct<ApplicationControlProperty> controlHolder = new(1);
- private List<ApplicationData> GetApplicationsFromPfs(IFileSystem pfs, string filePath)
- {
- var applications = new List<ApplicationData>();
- string extension = Path.GetExtension(filePath).ToLower();
+ try
+ {
+ string extension = Path.GetExtension(applicationPath).ToLower();
- foreach ((ulong titleId, ContentCollection content) in pfs.GetApplicationData(_virtualFileSystem, _checkLevel))
- {
- ApplicationData applicationData = new()
- {
- Id = titleId,
- };
+ using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
- try
- {
- Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program);
- Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control);
+ if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
+ {
+ try
+ {
+ IFileSystem pfs;
- BlitStruct<ApplicationControlProperty> controlHolder = new(1);
+ bool isExeFs = false;
- IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+ if (extension == ".xci")
+ {
+ Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
- // Check if there is an update available.
- if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs))
- {
- // Replace the original ControlFs by the updated one.
- controlFs = updatedControlFs;
- }
+ pfs = xci.OpenPartition(XciPartitionType.Secure);
+ }
+ else
+ {
+ var pfsTemp = new PartitionFileSystem();
+ pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
+ pfs = pfsTemp;
- ReadControlData(controlFs, controlHolder.ByteSpan);
+ // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
+ bool hasMainNca = false;
- GetApplicationInformation(ref controlHolder.Value, ref applicationData);
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
+ {
+ if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
+ {
+ using UniqueRef<IFile> ncaFile = new();
- // Read the icon from the ControlFS and store it as a byte array
- try
- {
- using UniqueRef<IFile> icon = new();
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
- using MemoryStream stream = new();
+ // Some main NCAs don't have a data partition, so check if the partition exists before opening it
+ if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
+ {
+ hasMainNca = true;
- icon.Get.AsStream().CopyTo(stream);
- applicationData.Icon = stream.ToArray();
- }
- catch (HorizonResultException)
- {
- foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
- {
- if (entry.Name == "control.nacp")
- {
- continue;
- }
+ break;
+ }
+ }
+ else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
+ {
+ isExeFs = true;
+ }
+ }
- using var icon = new UniqueRef<IFile>();
+ if (!hasMainNca && !isExeFs)
+ {
+ numApplicationsFound--;
- controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ continue;
+ }
+ }
- using MemoryStream stream = new();
+ if (isExeFs)
+ {
+ applicationIcon = _nspIcon;
- icon.Get.AsStream().CopyTo(stream);
- applicationData.Icon = stream.ToArray();
+ using UniqueRef<IFile> npdmFile = new();
- if (applicationData.Icon != null)
- {
- break;
- }
- }
+ Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
- applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon;
- }
+ if (ResultFs.PathNotFound.Includes(result))
+ {
+ Npdm npdm = new(npdmFile.Get.AsStream());
- applicationData.ControlHolder = controlHolder;
+ titleName = npdm.TitleName;
+ titleId = npdm.Aci0.TitleId.ToString("x16");
+ }
+ }
+ else
+ {
+ GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
- applications.Add(applicationData);
- }
- catch (MissingKeyException exception)
- {
- applicationData.Icon = extension == ".xci" ? _xciIcon : _nspIcon;
+ // Check if there is an update available.
+ if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
+ {
+ // Replace the original ControlFs by the updated one.
+ controlFs = updatedControlFs;
+ }
- Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
- }
- catch (InvalidDataException)
- {
- applicationData.Icon = extension == ".xci" ? _xciIcon : _nspIcon;
+ ReadControlData(controlFs, controlHolder.ByteSpan);
- Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {filePath}");
- }
- catch (Exception exception)
- {
- Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception}");
- }
- }
+ GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
- return applications;
- }
+ // Read the icon from the ControlFS and store it as a byte array
+ try
+ {
+ using UniqueRef<IFile> icon = new();
- private bool TryGetApplicationsFromFile(string applicationPath, out List<ApplicationData> applications)
- {
- applications = new List<ApplicationData>();
+ controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- long fileSizeBytes = new FileInfo(applicationPath).Length;
+ using MemoryStream stream = new();
- double fileSize = fileSizeBytes * 0.000000000931;
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+ }
+ catch (HorizonResultException)
+ {
+ foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
+ {
+ if (entry.Name == "control.nacp")
+ {
+ continue;
+ }
- BlitStruct<ApplicationControlProperty> controlHolder = new(1);
+ using var icon = new UniqueRef<IFile>();
- try
- {
- string extension = Path.GetExtension(applicationPath).ToLower();
+ controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
+ using MemoryStream stream = new();
- switch (extension)
- {
- case ".xci":
- {
- Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
- applications = GetApplicationsFromPfs(xci.OpenPartition(XciPartitionType.Secure), applicationPath);
+ if (applicationIcon != null)
+ {
+ break;
+ }
+ }
- if (applications.Count == 0)
+ applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
+ }
+ }
+ }
+ catch (MissingKeyException exception)
{
- return false;
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
+
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
}
+ catch (InvalidDataException)
+ {
+ applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
- break;
- }
- case ".nsp":
- case ".pfs0":
- var pfs = new PartitionFileSystem();
- pfs.Initialize(file.AsStorage()).ThrowIfFailure();
+ Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
+ }
+ catch (Exception exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
- ApplicationData result = GetApplicationFromNsp(pfs, applicationPath);
+ numApplicationsFound--;
- if (result == null)
- {
- return false;
+ continue;
+ }
}
-
- applications.Add(result);
-
- break;
- case ".nro":
+ else if (extension == ".nro")
{
BinaryReader reader = new(file);
- ApplicationData application = new();
byte[] Read(long position, int size)
{
@@ -333,54 +348,46 @@ namespace Ryujinx.Ui.App.Common
// Reads and stores game icon as byte array
if (iconSize > 0)
{
- application.Icon = Read(assetOffset + iconOffset, (int)iconSize);
+ applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
}
else
{
- application.Icon = _nroIcon;
+ applicationIcon = _nroIcon;
}
// Read the NACP data
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
- GetApplicationInformation(ref controlHolder.Value, ref application);
+ GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
}
else
{
- application.Icon = _nroIcon;
- application.Name = Path.GetFileNameWithoutExtension(applicationPath);
+ applicationIcon = _nroIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
}
-
- application.ControlHolder = controlHolder;
- applications.Add(application);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
- return false;
- }
+ numApplicationsFound--;
- break;
+ continue;
+ }
}
- case ".nca":
+ else if (extension == ".nca")
{
try
{
- ApplicationData application = new();
-
Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
- if (!nca.IsProgram() || nca.IsPatch())
+ if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
{
- return false;
- }
+ numApplicationsFound--;
- application.Icon = _ncaIcon;
- application.Name = Path.GetFileNameWithoutExtension(applicationPath);
- application.ControlHolder = controlHolder;
-
- applications.Add(application);
+ continue;
+ }
}
catch (InvalidDataException)
{
@@ -390,178 +397,78 @@ namespace Ryujinx.Ui.App.Common
{
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
- return false;
+ numApplicationsFound--;
+
+ continue;
}
- break;
+ applicationIcon = _ncaIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
}
- // If its an NSO we just set defaults
- case ".nso":
+ // If its an NSO we just set defaults
+ else if (extension == ".nso")
{
- ApplicationData application = new()
- {
- Icon = _nsoIcon,
- Name = Path.GetFileNameWithoutExtension(applicationPath),
- };
-
- applications.Add(application);
- break;
+ applicationIcon = _nsoIcon;
+ titleName = Path.GetFileNameWithoutExtension(applicationPath);
}
- }
- }
- catch (IOException exception)
- {
- Logger.Warning?.Print(LogClass.Application, exception.Message);
-
- return false;
- }
-
- foreach (var data in applications)
- {
- ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
- {
- 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)
- {
- 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.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
- data.FileSize = new FileInfo(applicationPath).Length;
- data.Path = applicationPath;
- }
-
- return true;
- }
-
- public void CancelLoading()
- {
- _cancellationToken?.Cancel();
- }
-
- public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
- {
- using UniqueRef<IFile> controlFile = new();
-
- controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
- }
-
- public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
- {
- int numApplicationsFound = 0;
- int numApplicationsLoaded = 0;
-
- _desiredTitleLanguage = desiredTitleLanguage;
-
- _cancellationToken = new CancellationTokenSource();
-
- // Builds the applications list with paths to found applications
- List<string> applicationPaths = new();
-
- try
- {
- foreach (string appDir in appDirs)
- {
- if (_cancellationToken.Token.IsCancellationRequested)
+ catch (IOException exception)
{
- return;
- }
+ Logger.Warning?.Print(LogClass.Application, exception.Message);
- if (!Directory.Exists(appDir))
- {
- Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
+ numApplicationsFound--;
continue;
}
- try
+ ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
{
- IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", SearchOption.AllDirectories).Where(file =>
- {
- return
- (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value) ||
- (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value) ||
- (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value) ||
- (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value) ||
- (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value) ||
- (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value);
- });
+ appMetadata.Title = titleName;
- foreach (string app in files)
+ // 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)
{
- if (_cancellationToken.Token.IsCancellationRequested)
- {
- return;
- }
-
- var fileInfo = new FileInfo(app);
- string extension = fileInfo.Extension.ToLower();
-
- if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
- {
- var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
- applicationPaths.Add(fullPath);
- numApplicationsFound++;
- }
+ appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
+ appMetadata.TimePlayedOld = default;
}
- }
- catch (UnauthorizedAccessException)
- {
- Logger.Warning?.Print(LogClass.Application, $"Failed to get access to directory: \"{appDir}\"");
- }
- }
- // Loops through applications list, creating a struct and then firing an event containing the struct for each application
- foreach (string applicationPath in applicationPaths)
- {
- if (_cancellationToken.Token.IsCancellationRequested)
- {
- return;
- }
-
- if (TryGetApplicationsFromFile(applicationPath, out List<ApplicationData> applications))
- {
- foreach (var application in applications)
+ // 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)
{
- OnApplicationAdded(new ApplicationAddedEventArgs
+ // Migrate from string-based last_played to DateTime-based last_played_utc.
+ if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
{
- AppData = application,
- });
- }
+ appMetadata.LastPlayed = lastPlayedOldParsed;
+
+ // Migration successful: deleting last_played from the metadata file.
+ appMetadata.LastPlayedOld = default;
+ }
- if (applications.Count > 1)
- {
- numApplicationsFound += applications.Count - 1;
}
+ });
- numApplicationsLoaded += applications.Count;
- }
- else
+ ApplicationData data = new()
{
- numApplicationsFound--;
- }
+ Favorite = appMetadata.Favorite,
+ Icon = applicationIcon,
+ TitleName = titleName,
+ TitleId = titleId,
+ Developer = developer,
+ Version = version,
+ TimePlayed = appMetadata.TimePlayed,
+ LastPlayed = appMetadata.LastPlayed,
+ FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(),
+ FileSize = fileSize,
+ Path = applicationPath,
+ ControlHolder = controlHolder,
+ };
+
+ numApplicationsLoaded++;
+
+ OnApplicationAdded(new ApplicationAddedEventArgs
+ {
+ AppData = data,
+ });
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
{
@@ -593,6 +500,15 @@ namespace Ryujinx.Ui.App.Common
ApplicationCountUpdated?.Invoke(null, e);
}
+ private void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem controlFs, out string titleId)
+ {
+ (_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
+
+ // Return the ControlFS
+ controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+ titleId = controlNca?.Header.TitleId.ToString("x16");
+ }
+
public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
{
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
@@ -630,29 +546,10 @@ namespace Ryujinx.Ui.App.Common
return appMetadata;
}
- public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage, ulong titleId)
+ public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage)
{
byte[] applicationIcon = null;
- if (titleId == 0)
- {
- if (Directory.Exists(applicationPath))
- {
- return _ncaIcon;
- }
-
- return Path.GetExtension(applicationPath).ToLower() switch
- {
- ".nsp" => _nspIcon,
- ".pfs0" => _nspIcon,
- ".xci" => _xciIcon,
- ".nso" => _nsoIcon,
- ".nro" => _nroIcon,
- ".nca" => _ncaIcon,
- _ => _ncaIcon,
- };
- }
-
try
{
// Look for icon only if applicationPath is not a directory
@@ -698,16 +595,7 @@ namespace Ryujinx.Ui.App.Common
else
{
// Store the ControlFS in variable called controlFs
- Dictionary<ulong, ContentCollection> programs = pfs.GetApplicationData(_virtualFileSystem, _checkLevel);
- IFileSystem controlFs = null;
-
- if (programs.ContainsKey(titleId))
- {
- if (programs[titleId].GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control) is { } controlNca)
- {
- controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
- }
- }
+ GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out _);
// Read the icon from the ControlFS and store it as a byte array
try
@@ -734,11 +622,16 @@ namespace Ryujinx.Ui.App.Common
controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- using MemoryStream stream = new();
- icon.Get.AsStream().CopyTo(stream);
- applicationIcon = stream.ToArray();
+ using (MemoryStream stream = new())
+ {
+ icon.Get.AsStream().CopyTo(stream);
+ applicationIcon = stream.ToArray();
+ }
- break;
+ if (applicationIcon != null)
+ {
+ break;
+ }
}
applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
@@ -821,41 +714,41 @@ namespace Ryujinx.Ui.App.Common
return applicationIcon ?? _ncaIcon;
}
- private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
+ private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
{
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{
- data.Name = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
- data.Developer = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
+ titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
+ publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
}
else
{
- data.Name = null;
- data.Developer = null;
+ titleName = null;
+ publisher = null;
}
- if (string.IsNullOrWhiteSpace(data.Name))
+ if (string.IsNullOrWhiteSpace(titleName))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.NameString.IsEmpty())
{
- data.Name = controlTitle.NameString.ToString();
+ titleName = controlTitle.NameString.ToString();
break;
}
}
}
- if (string.IsNullOrWhiteSpace(data.Developer))
+ if (string.IsNullOrWhiteSpace(publisher))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.PublisherString.IsEmpty())
{
- data.Developer = controlTitle.PublisherString.ToString();
+ publisher = controlTitle.PublisherString.ToString();
break;
}
@@ -864,21 +757,25 @@ namespace Ryujinx.Ui.App.Common
if (controlData.PresenceGroupId != 0)
{
- data.Id = controlData.PresenceGroupId;
+ titleId = controlData.PresenceGroupId.ToString("x16");
}
else if (controlData.SaveDataOwnerId != 0)
{
- data.Id = controlData.SaveDataOwnerId;
+ titleId = controlData.SaveDataOwnerId.ToString();
}
else if (controlData.AddOnContentBaseId != 0)
{
- data.Id = (controlData.AddOnContentBaseId - 0x1000);
+ titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
+ }
+ else
+ {
+ titleId = "0000000000000000";
}
- data.Version = controlData.DisplayVersionString.ToString();
+ version = controlData.DisplayVersionString.ToString();
}
- private bool IsUpdateApplied(Nca mainNca, out IFileSystem updatedControlFs)
+ private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
{
updatedControlFs = null;
@@ -886,11 +783,11 @@ namespace Ryujinx.Ui.App.Common
try
{
- (Nca patchNca, Nca controlNca) = mainNca.GetUpdateData(_virtualFileSystem, _checkLevel, 0, out updatePath);
+ (Nca patchNca, Nca controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
if (patchNca != null && controlNca != null)
{
- updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+ updatedControlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
return true;
}
@@ -906,5 +803,120 @@ namespace Ryujinx.Ui.App.Common
return false;
}
+
+ public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
+ {
+ Nca mainNca = null;
+ Nca patchNca = null;
+ Nca controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef<IFile>();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+ {
+ patchNca = nca;
+ }
+ else
+ {
+ mainNca = nca;
+ }
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (mainNca, patchNca, controlNca);
+ }
+
+ public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
+ {
+ Nca patchNca = null;
+ Nca controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef<IFile>();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
+ {
+ break;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ patchNca = nca;
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (patchNca, controlNca);
+ }
+
+ public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
+ {
+ updatePath = null;
+
+ if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+ {
+ // Clear the program index part.
+ titleIdBase &= ~0xFUL;
+
+ // Load update information if exists.
+ string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+
+ if (File.Exists(titleUpdateMetadataPath))
+ {
+ updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
+
+ if (File.Exists(updatePath))
+ {
+ FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
+ PartitionFileSystem nsp = new();
+ nsp.Initialize(file.AsStorage()).ThrowIfFailure();
+
+ return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
+ }
+ }
+ }
+
+ return (null, null);
+ }
}
}
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index 14062481..afb6a992 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -7,7 +7,6 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui;
-using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@@ -333,12 +332,7 @@ namespace Ryujinx
if (CommandLineState.LaunchPathArg != null)
{
- ApplicationData applicationData = new()
- {
- Path = CommandLineState.LaunchPathArg,
- };
-
- mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
+ mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs
index 884f6687..8b0b35e6 100644
--- a/src/Ryujinx/Ui/MainWindow.cs
+++ b/src/Ryujinx/Ui/MainWindow.cs
@@ -39,7 +39,6 @@ using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using System;
using System.Diagnostics;
-using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -71,7 +70,7 @@ namespace Ryujinx.Ui
private bool _gameLoaded;
private bool _ending;
- private ApplicationData _currentApplicationData = null;
+ private string _currentEmulatedGamePath = null;
private string _lastScannedAmiiboId = "";
private bool _lastScannedAmiiboShowAll = false;
@@ -182,12 +181,8 @@ namespace Ryujinx.Ui
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
_userChannelPersistence = new UserChannelPersistence();
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
// Instantiate GUI objects.
- _applicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
+ _applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
_uiHandler = new GtkHostUiHandler(this);
_deviceExitStatus = new AutoResetEvent(false);
@@ -789,7 +784,7 @@ namespace Ryujinx.Ui
}
}
- private bool LoadApplication(string path, ulong titleId, bool isFirmwareTitle)
+ private bool LoadApplication(string path, bool isFirmwareTitle)
{
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
@@ -863,7 +858,7 @@ namespace Ryujinx.Ui
case ".xci":
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
- return _emulationContext.LoadXci(path, titleId);
+ return _emulationContext.LoadXci(path);
case ".nca":
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
@@ -872,7 +867,7 @@ namespace Ryujinx.Ui
case ".pfs0":
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
- return _emulationContext.LoadNsp(path, titleId);
+ return _emulationContext.LoadNsp(path);
default:
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
try
@@ -893,7 +888,7 @@ namespace Ryujinx.Ui
return false;
}
- public void RunApplication(ApplicationData application, bool startFullscreen = false)
+ public void RunApplication(string path, bool startFullscreen = false)
{
if (_gameLoaded)
{
@@ -915,14 +910,14 @@ namespace Ryujinx.Ui
bool isFirmwareTitle = false;
- if (application.Path.StartsWith("@SystemContent"))
+ if (path.StartsWith("@SystemContent"))
{
- application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
+ path = VirtualFileSystem.SwitchPathToSystemPath(path);
isFirmwareTitle = true;
}
- if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
+ if (!LoadApplication(path, isFirmwareTitle))
{
_emulationContext.Dispose();
SwitchToGameTable();
@@ -932,7 +927,7 @@ namespace Ryujinx.Ui
SetupProgressUiHandlers();
- _currentApplicationData = application;
+ _currentEmulatedGamePath = path;
_deviceExitStatus.Reset();
@@ -1173,7 +1168,7 @@ namespace Ryujinx.Ui
_tableStore.AppendValues(
args.AppData.Favorite,
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
- $"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
+ $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
args.AppData.Developer,
args.AppData.Version,
args.AppData.TimePlayedString,
@@ -1261,22 +1256,9 @@ namespace Ryujinx.Ui
{
_gameTableSelection.GetSelected(out TreeIter treeIter);
- ApplicationData application = new()
- {
- Favorite = (bool)_tableStore.GetValue(treeIter, 0),
- Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
- Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
- Developer = (string)_tableStore.GetValue(treeIter, 3),
- Version = (string)_tableStore.GetValue(treeIter, 4),
- TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
- LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
- FileExtension = (string)_tableStore.GetValue(treeIter, 7),
- FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
- Path = (string)_tableStore.GetValue(treeIter, 9),
- ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
- };
+ string path = (string)_tableStore.GetValue(treeIter, 9);
- RunApplication(application);
+ RunApplication(path);
}
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
@@ -1334,22 +1316,13 @@ namespace Ryujinx.Ui
return;
}
- ApplicationData application = new()
- {
- Favorite = (bool)_tableStore.GetValue(treeIter, 0),
- Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
- Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
- Developer = (string)_tableStore.GetValue(treeIter, 3),
- Version = (string)_tableStore.GetValue(treeIter, 4),
- TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
- LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
- FileExtension = (string)_tableStore.GetValue(treeIter, 7),
- FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
- Path = (string)_tableStore.GetValue(treeIter, 9),
- ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
- };
+ string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
+ string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
+ string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
+
+ BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
- _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
+ _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
}
private void Load_Application_File(object sender, EventArgs args)
@@ -1371,12 +1344,7 @@ namespace Ryujinx.Ui
if (fileChooser.Run() == (int)ResponseType.Accept)
{
- ApplicationData applicationData = new()
- {
- Path = fileChooser.Filename,
- };
-
- RunApplication(applicationData);
+ RunApplication(fileChooser.Filename);
}
}
@@ -1386,13 +1354,7 @@ namespace Ryujinx.Ui
if (fileChooser.Run() == (int)ResponseType.Accept)
{
- ApplicationData applicationData = new()
- {
- Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
- Path = fileChooser.Filename,
- };
-
- RunApplication(applicationData);
+ RunApplication(fileChooser.Filename);
}
}
@@ -1407,14 +1369,7 @@ namespace Ryujinx.Ui
{
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
- ApplicationData applicationData = new()
- {
- Name = "miiEdit",
- Id = 0x0100000000001009ul,
- Path = contentPath,
- };
-
- RunApplication(applicationData);
+ RunApplication(contentPath);
}
private void Open_Ryu_Folder(object sender, EventArgs args)
@@ -1690,13 +1645,13 @@ namespace Ryujinx.Ui
{
_userChannelPersistence.ShouldRestart = false;
- RunApplication(_currentApplicationData);
+ RunApplication(_currentEmulatedGamePath);
}
else
{
// otherwise, clear state.
_userChannelPersistence = new UserChannelPersistence();
- _currentApplicationData = null;
+ _currentEmulatedGamePath = null;
_actionMenu.Sensitive = false;
_firmwareInstallFile.Sensitive = true;
_firmwareInstallDirectory.Sensitive = true;
@@ -1758,7 +1713,7 @@ namespace Ryujinx.Ui
_emulationContext.Processes.ActiveApplication.ProgramId,
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
- _currentApplicationData.Path);
+ _currentEmulatedGamePath);
window.Destroyed += CheatWindow_Destroyed;
window.Show();
diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index 6903c941..5af181b0 100644
--- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -16,7 +16,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@@ -24,6 +23,7 @@ using Ryujinx.Ui.Windows;
using System;
using System.Buffers;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -36,13 +36,17 @@ namespace Ryujinx.Ui.Widgets
private readonly VirtualFileSystem _virtualFileSystem;
private readonly AccountManager _accountManager;
private readonly HorizonClient _horizonClient;
+ private readonly BlitStruct<ApplicationControlProperty> _controlData;
- private readonly ApplicationData _title;
+ private readonly string _titleFilePath;
+ private readonly string _titleName;
+ private readonly string _titleIdText;
+ private readonly ulong _titleId;
private MessageDialog _dialog;
private bool _cancel;
- public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData)
+ public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
{
_parent = parent;
@@ -51,13 +55,23 @@ namespace Ryujinx.Ui.Widgets
_virtualFileSystem = virtualFileSystem;
_accountManager = accountManager;
_horizonClient = horizonClient;
- _title = applicationData;
+ _titleFilePath = titleFilePath;
+ _titleName = titleName;
+ _titleIdText = titleId;
+ _controlData = controlData;
- _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(_title.ControlHolder.ByteSpan) && _title.ControlHolder.Value.UserAccountSaveDataSize > 0;
- _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(_title.ControlHolder.ByteSpan) && _title.ControlHolder.Value.DeviceSaveDataSize > 0;
- _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(_title.ControlHolder.ByteSpan) && _title.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
+ if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
+ {
+ GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
+
+ return;
+ }
+
+ _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
+ _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
+ _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
- string fileExt = System.IO.Path.GetExtension(_title.Path).ToLower();
+ string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
_extractRomFsMenuItem.Sensitive = hasNca;
@@ -123,7 +137,7 @@ namespace Ryujinx.Ui.Widgets
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
{
- if (!TryFindSaveData(_title.Name, _title.Id, _title.ControlHolder, in saveDataFilter, out ulong saveDataId))
+ if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
{
return;
}
@@ -176,7 +190,7 @@ namespace Ryujinx.Ui.Widgets
{
Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"),
- SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_title.Path)}...",
+ SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
WindowPosition = WindowPosition.Center,
};
@@ -188,18 +202,18 @@ namespace Ryujinx.Ui.Widgets
}
});
- using FileStream file = new(_title.Path, FileMode.Open, FileAccess.Read);
+ using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
- if ((System.IO.Path.GetExtension(_title.Path).ToLower() == ".nsp") ||
- (System.IO.Path.GetExtension(_title.Path).ToLower() == ".pfs0") ||
- (System.IO.Path.GetExtension(_title.Path).ToLower() == ".xci"))
+ if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
+ (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
+ (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
{
IFileSystem pfs;
- if (System.IO.Path.GetExtension(_title.Path).ToLower() == ".xci")
+ if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
{
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
@@ -235,7 +249,7 @@ namespace Ryujinx.Ui.Widgets
}
}
}
- else if (System.IO.Path.GetExtension(_title.Path).ToLower() == ".nca")
+ else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
{
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
}
@@ -252,11 +266,7 @@ namespace Ryujinx.Ui.Widgets
return;
}
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
- (Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
+ (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
{
@@ -450,44 +460,44 @@ namespace Ryujinx.Ui.Widgets
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
- var saveDataFilter = SaveDataFilter.Make(_title.Id, saveType: default, userId, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
{
- var saveDataFilter = SaveDataFilter.Make(_title.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
{
- var saveDataFilter = SaveDataFilter.Make(_title.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
{
- new TitleUpdateWindow(_parent, _virtualFileSystem, _title).Show();
+ new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
}
private void ManageDlc_Clicked(object sender, EventArgs args)
{
- new DlcWindow(_virtualFileSystem, _title.IdString, _title).Show();
+ new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
}
private void ManageCheats_Clicked(object sender, EventArgs args)
{
- new CheatWindow(_virtualFileSystem, _title.Id, _title.Name, _title.Path).Show();
+ new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
}
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
{
string modsBasePath = ModLoader.GetModsBasePath();
- string titleModsPath = ModLoader.GetTitleDir(modsBasePath, _title.IdString);
+ string titleModsPath = ModLoader.GetTitleDir(modsBasePath, _titleIdText);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -495,7 +505,7 @@ namespace Ryujinx.Ui.Widgets
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
- string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, _title.IdString);
+ string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, _titleIdText);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -517,7 +527,7 @@ namespace Ryujinx.Ui.Widgets
private void OpenPtcDir_Clicked(object sender, EventArgs args)
{
- string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _title.IdString, "cache", "cpu");
+ string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
string mainPath = System.IO.Path.Combine(ptcDir, "0");
string backupPath = System.IO.Path.Combine(ptcDir, "1");
@@ -534,7 +544,7 @@ namespace Ryujinx.Ui.Widgets
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
{
- string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _title.IdString, "cache", "shader");
+ string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
@@ -546,10 +556,10 @@ namespace Ryujinx.Ui.Widgets
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
- DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _title.IdString, "cache", "cpu", "0"));
- DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _title.IdString, "cache", "cpu", "1"));
+ DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
- MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_title.Name}</b>\n\nAre you sure you want to proceed?");
+ MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
List<FileInfo> cacheFiles = new();
@@ -583,9 +593,9 @@ namespace Ryujinx.Ui.Widgets
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
{
- DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _title.IdString, "cache", "shader"));
+ DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
- using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_title.Name}</b>\n\nAre you sure you want to proceed?");
+ using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
@@ -627,11 +637,8 @@ namespace Ryujinx.Ui.Widgets
private void CreateShortcut_Clicked(object sender, EventArgs args)
{
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
- byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_title.Path, ConfigurationState.Instance.System.Language, _title.Id);
- ShortcutHelper.CreateAppShortcut(_title.Path, _title.Name, _title.IdString, appIcon);
+ byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
+ ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
}
}
}
diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.cs b/src/Ryujinx/Ui/Windows/CheatWindow.cs
index 9bbae1c6..1eca732b 100644
--- a/src/Ryujinx/Ui/Windows/CheatWindow.cs
+++ b/src/Ryujinx/Ui/Windows/CheatWindow.cs
@@ -1,9 +1,7 @@
using Gtk;
-using LibHac.Tools.FsSystem;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
@@ -29,13 +27,8 @@ namespace Ryujinx.Ui.Windows
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
{
builder.Autoconnect(this);
-
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
- _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}";
+ _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16"));
diff --git a/src/Ryujinx/Ui/Windows/DlcWindow.cs b/src/Ryujinx/Ui/Windows/DlcWindow.cs
index dbffc420..9f717946 100644
--- a/src/Ryujinx/Ui/Windows/DlcWindow.cs
+++ b/src/Ryujinx/Ui/Windows/DlcWindow.cs
@@ -9,12 +9,9 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
-using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using GUI = Gtk.Builder.ObjectAttribute;
@@ -23,7 +20,7 @@ namespace Ryujinx.Ui.Windows
public class DlcWindow : Window
{
private readonly VirtualFileSystem _virtualFileSystem;
- private readonly string _applicationId;
+ private readonly string _titleId;
private readonly string _dlcJsonPath;
private readonly List<DownloadableContentContainer> _dlcContainerList;
@@ -35,16 +32,16 @@ namespace Ryujinx.Ui.Windows
[GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044
- public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, ApplicationData title) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, title) { }
+ public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
- private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationId, ApplicationData title) : base(builder.GetRawOwnedObject("_dlcWindow"))
+ private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
{
builder.Autoconnect(this);
- _applicationId = applicationId;
+ _titleId = titleId;
_virtualFileSystem = virtualFileSystem;
- _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json");
- _baseTitleInfoLabel.Text = $"DLC Available for {title.Name} [{applicationId.ToUpper()}]";
+ _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
+ _baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
try
{
@@ -75,12 +72,9 @@ namespace Ryujinx.Ui.Windows
};
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
- _dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1);
+ _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
- // NOTE: Try to load downloadable contents from PFS first.
- AddDlc(title.Path, true);
-
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
{
if (File.Exists(dlcContainer.ContainerPath))
@@ -95,10 +89,7 @@ namespace Ryujinx.Ui.Windows
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
PartitionFileSystem pfs = new();
- if (pfs.Initialize(containerFile.AsStorage()).IsFailure())
- {
- continue;
- }
+ pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(pfs);
@@ -137,57 +128,6 @@ namespace Ryujinx.Ui.Windows
return null;
}
- private void AddDlc(string path, bool ignoreNotFound = false)
- {
- if (!File.Exists(path))
- {
- return;
- }
-
- using FileStream containerFile = File.OpenRead(path);
-
- PartitionFileSystem pfs = new();
- pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
-
- bool containsDlc = false;
-
- _virtualFileSystem.ImportTickets(pfs);
-
- TreeIter? parentIter = null;
-
- foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
- {
- using var ncaFile = new UniqueRef<IFile>();
-
- pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
-
- if (nca == null)
- {
- continue;
- }
-
- if (nca.Header.ContentType == NcaContentType.PublicData)
- {
- if (nca.GetProgramIdBase() != (ulong.Parse(_applicationId, NumberStyles.HexNumber) & ~0x1FFFUL))
- {
- break;
- }
-
- parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path);
-
- ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
- containsDlc = true;
- }
- }
-
- if (!containsDlc && !ignoreNotFound)
- {
- GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
- }
- }
-
private void AddButton_Clicked(object sender, EventArgs args)
{
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
@@ -207,7 +147,52 @@ namespace Ryujinx.Ui.Windows
{
foreach (string containerPath in fileChooser.Filenames)
{
- AddDlc(containerPath);
+ if (!File.Exists(containerPath))
+ {
+ return;
+ }
+
+ using FileStream containerFile = File.OpenRead(containerPath);
+
+ PartitionFileSystem pfs = new();
+ pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
+ bool containsDlc = false;
+
+ _virtualFileSystem.ImportTickets(pfs);
+
+ TreeIter? parentIter = null;
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef<IFile>();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
+
+ if (nca == null)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.PublicData)
+ {
+ if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
+ {
+ break;
+ }
+
+ parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
+
+ ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
+ containsDlc = true;
+ }
+ }
+
+ if (!containsDlc)
+ {
+ GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
+ }
}
}
diff --git a/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
index 2f7f14f1..51918eea 100644
--- a/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
+++ b/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
@@ -4,15 +4,12 @@ using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ns;
-using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.Ui.App.Common;
-using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
@@ -27,7 +24,7 @@ namespace Ryujinx.Ui.Windows
{
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem;
- private readonly ApplicationData _title;
+ private readonly string _titleId;
private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
@@ -41,17 +38,17 @@ namespace Ryujinx.Ui.Windows
[GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044
- public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { }
+ public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
- private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
+ private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
{
_parent = parent;
builder.Autoconnect(this);
- _title = applicationData;
+ _titleId = titleId;
_virtualFileSystem = virtualFileSystem;
- _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "updates.json");
+ _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
try
@@ -67,10 +64,7 @@ namespace Ryujinx.Ui.Windows
};
}
- _baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdString}]";
-
- // Try to get updates from PFS first
- AddUpdate(_title.Path, true);
+ _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
foreach (string path in _titleUpdateWindowData.Paths)
{
@@ -90,41 +84,18 @@ namespace Ryujinx.Ui.Windows
}
}
- private void AddUpdate(string path, bool ignoreNotFound = false)
+ private void AddUpdate(string path)
{
if (File.Exists(path))
{
- IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
- ? IntegrityCheckLevel.ErrorOnInvalid
- : IntegrityCheckLevel.None;
-
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
- IFileSystem pfs;
+ PartitionFileSystem nsp = new();
+ nsp.Initialize(file.AsStorage()).ThrowIfFailure();
try
{
- if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
- {
- pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
- }
- else
- {
- var pfsTemp = new PartitionFileSystem();
- pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
- pfs = pfsTemp;
- }
-
- Dictionary<ulong, ContentCollection> updates = pfs.GetUpdateData(_virtualFileSystem, checkLevel);
-
- Nca patchNca = null;
- Nca controlNca = null;
-
- if (updates.TryGetValue(_title.Id, out ContentCollection update))
- {
- patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program);
- controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control);
- }
+ (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
if (controlNca != null && patchNca != null)
{
@@ -135,14 +106,7 @@ namespace Ryujinx.Ui.Windows
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
- string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}";
-
- if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
- {
- radioLabel = "Bundled: " + radioLabel;
- }
-
- RadioButton radioButton = new(radioLabel);
+ RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}");
radioButton.JoinGroup(_noUpdateRadioButton);
_availableUpdatesBox.Add(radioButton);
@@ -153,10 +117,7 @@ namespace Ryujinx.Ui.Windows
}
else
{
- if (!ignoreNotFound)
- {
- GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
- }
+ GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
}
}
catch (Exception exception)