mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-24 10:47:11 +02:00
Add support for multi game XCIs (second try) (#6515)
* Add default values to ApplicationData directly * Refactor application loading It should now be possible to load multi game XCIs. Included updates won't be detected for now. Opening a game from the command line currently only opens the first one. * Only include program NCAs where at least one tuple item is not null * Get application data by title id and add programIndex check back * Refactor application loading again and remove duplicate code * Actually use patch ncas for updates * Fix number of applications found with multi game xcis * Don't load bundled updates from multi game xcis * Change ApplicationData.TitleId type to ulong & Add TitleIdString property * Use cnmt files and ContentCollection to load programs * Ava: Add updates and DLCs from gamecarts * Get the cnmt file from its NCA * Ava: Identify bundled updates in updater window * Fix the (hopefully) last few bugs * Add idOffset parameter to GetNcaByType * Handle missing file for dlc.json * Ava: Shorten error message for invalid files * Gtk: Add additional string for bundled updates in TitleUpdateWindow * Hopefully fix DLC issues * Apply formatting * Finally fix DLC issues * Adjust property names and fileSize field * Read the correct update file * Fix wrong casing for application id strings * Rename TitleId to ApplicationId * Address review comments * Apply suggestions from code review Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Gracefully fail when loading pfs for update and dlc window * Fix applications with multiple programs * Fix DLCWindow crash on GTK * Fix some GUI issues * Remove IsXci again * Don't add duplicates to update/dlc windows * Avoid double lookup * Preserve DLC enabled state for bundled DLCs * Fix DLCWindow not opening using GTK * Fix missing information when loading applications from file * Address review feedback Rename ContentCollection to ContentMetaData Fix casing issues in log messages Use null as the default value for updatePath * Fix re-adding bundled DLCs every time * Fix bundled DLCs disappearing * Abstract common code to open application pfs * Remove unused imports * Fix file exists check when loading DLCs * Load bundled DLCs only using dlc.json * Load AoC items correctly * Add all DLCs from a PFS * Add argument to launch a specific application id * Use application-id argument for shortcuts if necessary * Return the application id from the control NCA if possible * GetApplicationInformation: Don't overwrite application ids Move SaveDataOwnerId check to the top, since it seems to be more reliable. * Get application ids from CNMT again This commit reverts some parts of 61615b8f0d6f90ae86778958ddc38eaf6dc280ab. Since the issue wasn't actually related to the application id in CMNTs, we can remove the wrong assumptions. * Revert erroneous axaml change from adca8900 * Rename title to application * Wrap nsp/pfs0 case with curly braces * Check if _applicationData.ControlHolder.ByteSpan is zeros only once * Catch exceptions while loading applications from nsps --------- Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
parent
344f4f52c1
commit
6fbf279fac
38 changed files with 1299 additions and 961 deletions
|
@ -1,7 +1,6 @@
|
|||
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;
|
||||
|
@ -15,7 +14,6 @@ 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;
|
||||
|
||||
|
@ -41,7 +39,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
{
|
||||
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
|
||||
|
||||
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
|
||||
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
|
||||
{
|
||||
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
|
||||
});
|
||||
|
@ -76,19 +74,9 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
{
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
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]);
|
||||
});
|
||||
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
|
||||
|
||||
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
|
||||
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
||||
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +96,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
||||
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +108,8 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
{
|
||||
await new CheatWindow(
|
||||
viewModel.VirtualFileSystem,
|
||||
viewModel.SelectedApplication.TitleId,
|
||||
viewModel.SelectedApplication.TitleName,
|
||||
viewModel.SelectedApplication.IdString,
|
||||
viewModel.SelectedApplication.Name,
|
||||
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
|
@ -146,7 +134,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
|
@ -158,7 +146,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
await ModManagerWindow.Show(ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
||||
await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,15 +158,15 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
|
||||
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.TitleId, "cache", "cpu", "0"));
|
||||
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
|
||||
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"));
|
||||
|
||||
List<FileInfo> cacheFiles = new();
|
||||
|
||||
|
@ -218,14 +206,14 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
|
||||
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.TitleId, "cache", "shader"));
|
||||
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
|
||||
|
||||
List<DirectoryInfo> oldCacheDirectories = new();
|
||||
List<FileInfo> newCacheFiles = new();
|
||||
|
@ -273,7 +261,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu");
|
||||
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
|
||||
string mainDir = Path.Combine(ptcDir, "0");
|
||||
string backupDir = Path.Combine(ptcDir, "1");
|
||||
|
||||
|
@ -294,7 +282,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader");
|
||||
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
|
||||
|
||||
if (!Directory.Exists(shaderCacheDir))
|
||||
{
|
||||
|
@ -315,7 +303,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
viewModel.StorageProvider,
|
||||
NcaSectionType.Code,
|
||||
viewModel.SelectedApplication.Path,
|
||||
viewModel.SelectedApplication.TitleName);
|
||||
viewModel.SelectedApplication.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +317,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
viewModel.StorageProvider,
|
||||
NcaSectionType.Data,
|
||||
viewModel.SelectedApplication.Path,
|
||||
viewModel.SelectedApplication.TitleName);
|
||||
viewModel.SelectedApplication.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +331,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
viewModel.StorageProvider,
|
||||
NcaSectionType.Logo,
|
||||
viewModel.SelectedApplication.Path,
|
||||
viewModel.SelectedApplication.TitleName);
|
||||
viewModel.SelectedApplication.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,7 +342,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
ApplicationData selectedApplication = viewModel.SelectedApplication;
|
||||
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
|
||||
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,7 +352,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
|
||||
await viewModel.LoadApplication(viewModel.SelectedApplication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TitleName}"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</Panel>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
FontWeight="Bold"
|
||||
Text="{Binding TitleName}"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
|
@ -109,7 +109,7 @@
|
|||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleId}"
|
||||
Text="{Binding Id, StringFormat=X16}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using System.IO;
|
||||
|
||||
|
@ -24,6 +25,9 @@ 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;
|
||||
|
|
|
@ -46,14 +46,14 @@ namespace Ryujinx.Ava.UI.Models
|
|||
TitleId = info.ProgramId;
|
||||
UserId = info.UserId;
|
||||
|
||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString);
|
||||
|
||||
InGameList = appData != null;
|
||||
|
||||
if (InGameList)
|
||||
{
|
||||
Icon = appData.Icon;
|
||||
Title = appData.TitleName;
|
||||
Title = appData.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -8,7 +8,10 @@ namespace Ryujinx.Ava.UI.Models
|
|||
public ApplicationControlProperty Control { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
|
||||
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||
System.IO.Path.GetExtension(Path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel,
|
||||
Control.DisplayVersionString.ToString()
|
||||
);
|
||||
|
||||
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||
{
|
||||
|
|
|
@ -6,7 +6,6 @@ using DynamicData;
|
|||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
|
@ -17,11 +16,13 @@ using Ryujinx.Common.Configuration;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
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;
|
||||
|
||||
|
@ -38,7 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||
|
||||
private string _search;
|
||||
private readonly ulong _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
|
||||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
@ -91,18 +92,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||
}
|
||||
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationData = applicationData;
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
_storageProvider = desktop.MainWindow.StorageProvider;
|
||||
}
|
||||
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json");
|
||||
|
||||
if (!File.Exists(_downloadableContentJsonPath))
|
||||
{
|
||||
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -123,12 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||
{
|
||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(downloadableContentContainer.ContainerPath, _virtualFileSystem);
|
||||
|
||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||
{
|
||||
|
@ -157,6 +160,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
|
||||
AddDownloadableContent(_applicationData.Path);
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
Sort();
|
||||
|
@ -219,25 +225,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
foreach (var file in result)
|
||||
{
|
||||
await AddDownloadableContent(file.Path.LocalPath);
|
||||
if (!AddDownloadableContent(file.Path.LocalPath))
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddDownloadableContent(string path)
|
||||
private bool AddDownloadableContent(string path)
|
||||
{
|
||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||
if (!File.Exists(path) || _downloadableContentContainerList.Any(x => x.ContainerPath == path))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDownloadableContent = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||
|
||||
bool success = false;
|
||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
@ -252,26 +256,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||
if (nca.GetProgramIdBase() != _applicationData.IdBase)
|
||||
{
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
||||
DownloadableContents.Add(content);
|
||||
SelectedDownloadableContents.Add(content);
|
||||
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
|
||||
containsDownloadableContent = true;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDownloadableContent)
|
||||
if (success)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void Remove(DownloadableContentModel model)
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
private bool _canUpdate = true;
|
||||
private Cursor _cursor;
|
||||
private string _title;
|
||||
private string _currentEmulatedGamePath;
|
||||
private ApplicationData _currentApplicationData;
|
||||
private readonly AutoResetEvent _rendererWaitEvent;
|
||||
private WindowState _windowState;
|
||||
private double _windowWidth;
|
||||
|
@ -108,7 +108,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
public ApplicationData ListSelectedApplication;
|
||||
public ApplicationData GridSelectedApplication;
|
||||
|
||||
private string TitleName { get; set; }
|
||||
internal AppHost AppHost { get; set; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
|
@ -954,8 +953,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
return SortMode switch
|
||||
{
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Name),
|
||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||
|
@ -999,7 +998,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo;
|
||||
|
||||
return compareInfo.IndexOf(app.TitleName, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
|
||||
return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1128,7 +1127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case LoadState.Loaded:
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||
IsLoadingIndeterminate = true;
|
||||
CacheLoadStatus = "";
|
||||
break;
|
||||
|
@ -1148,7 +1147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case ShaderCacheLoadingState.Loaded:
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||
IsLoadingIndeterminate = true;
|
||||
CacheLoadStatus = "";
|
||||
break;
|
||||
|
@ -1200,13 +1199,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
{
|
||||
UserChannelPersistence.ShouldRestart = false;
|
||||
|
||||
await LoadApplication(_currentEmulatedGamePath);
|
||||
await LoadApplication(_currentApplicationData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, clear state.
|
||||
UserChannelPersistence = new UserChannelPersistence();
|
||||
_currentEmulatedGamePath = null;
|
||||
_currentApplicationData = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1493,7 +1492,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
if (result.Count > 0)
|
||||
{
|
||||
await LoadApplication(result[0].Path.LocalPath);
|
||||
if (ApplicationLibrary.TryGetApplicationsFromFile(result[0].Path.LocalPath,
|
||||
out List<ApplicationData> applications))
|
||||
{
|
||||
await LoadApplication(applications[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1507,11 +1514,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
if (result.Count > 0)
|
||||
{
|
||||
await LoadApplication(result[0].Path.LocalPath);
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
|
||||
Path = result[0].Path.LocalPath,
|
||||
};
|
||||
|
||||
await LoadApplication(applicationData);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
|
||||
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false)
|
||||
{
|
||||
if (AppHost != null)
|
||||
{
|
||||
|
@ -1531,7 +1544,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
Logger.RestartTime();
|
||||
|
||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
|
||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
||||
|
||||
PrepareLoadScreen();
|
||||
|
||||
|
@ -1540,7 +1553,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
AppHost = new AppHost(
|
||||
RendererHostControl,
|
||||
InputManager,
|
||||
path,
|
||||
application.Path,
|
||||
application.Id,
|
||||
VirtualFileSystem,
|
||||
ContentManager,
|
||||
AccountManager,
|
||||
|
@ -1558,17 +1572,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
CanUpdate = false;
|
||||
|
||||
LoadHeading = TitleName = titleName;
|
||||
LoadHeading = application.Name;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
if (string.IsNullOrWhiteSpace(application.Name))
|
||||
{
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||
application.Name = AppHost.Device.Processes.ActiveApplication.Name;
|
||||
}
|
||||
|
||||
SwitchToRenderer(startFullscreen);
|
||||
|
||||
_currentEmulatedGamePath = path;
|
||||
_currentApplicationData = application;
|
||||
|
||||
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
||||
gameThread.Start();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
@ -6,7 +5,7 @@ using Avalonia.Threading;
|
|||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
|
@ -17,12 +16,17 @@ using Ryujinx.Common.Configuration;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
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;
|
||||
|
||||
|
@ -33,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
public TitleUpdateMetadata TitleUpdateWindowData;
|
||||
public readonly string TitleUpdateJsonPath;
|
||||
private VirtualFileSystem VirtualFileSystem { get; }
|
||||
private ulong TitleId { get; }
|
||||
private ApplicationData ApplicationData { get; }
|
||||
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
|
@ -73,18 +77,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
public IStorageProvider StorageProvider;
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
|
||||
TitleId = titleId;
|
||||
ApplicationData = applicationData;
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||
}
|
||||
|
||||
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdString, "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -92,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdString} at {TitleUpdateJsonPath}");
|
||||
|
||||
TitleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
|
@ -108,6 +112,9 @@ 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);
|
||||
|
@ -162,38 +169,54 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
if (!File.Exists(path) || TitleUpdates.Any(x => x.Path == path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
try
|
||||
{
|
||||
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, VirtualFileSystem);
|
||||
|
||||
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, VirtualFileSystem, checkLevel);
|
||||
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
if (updates.TryGetValue(ApplicationData.Id, out ContentMetaData content))
|
||||
{
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
|
||||
patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
|
||||
controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
|
||||
}
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ignoreNotFound)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ 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;
|
||||
|
@ -134,7 +135,14 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||
|
||||
if (!string.IsNullOrEmpty(contentPath))
|
||||
{
|
||||
await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = "miiEdit",
|
||||
Id = 0x0100000000001009,
|
||||
Path = contentPath,
|
||||
};
|
||||
|
||||
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
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;
|
||||
|
@ -34,9 +36,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||
{
|
||||
LoadedCheats = new AvaloniaList<CheatNode>();
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
||||
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
|
||||
BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
MaxLines="2"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding FileName}" />
|
||||
Text="{Binding Label}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="10 0"
|
||||
|
|
|
@ -3,13 +3,12 @@ using Avalonia.Interactivity;
|
|||
using Avalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
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 +23,22 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId);
|
||||
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = "",
|
||||
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
|
||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
|
||||
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
|
||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdString),
|
||||
};
|
||||
|
||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||
|
|
|
@ -5,6 +5,7 @@ using Avalonia.Interactivity;
|
|||
using Avalonia.Platform;
|
||||
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,7 +25,7 @@ using Ryujinx.UI.Common;
|
|||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -40,6 +41,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
private UserChannelPersistence _userChannelPersistence;
|
||||
private static bool _deferLoad;
|
||||
private static string _launchPath;
|
||||
private static string _launchApplicationId;
|
||||
private static bool _startFullscreen;
|
||||
internal readonly AvaHostUIHandler UiHandler;
|
||||
|
||||
|
@ -168,18 +170,17 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
{
|
||||
ViewModel.SelectedIcon = args.Application.Icon;
|
||||
|
||||
string path = new FileInfo(args.Application.Path).FullName;
|
||||
|
||||
ViewModel.LoadApplication(path).Wait();
|
||||
ViewModel.LoadApplication(args.Application).Wait();
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
|
||||
internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg)
|
||||
{
|
||||
_deferLoad = true;
|
||||
_launchPath = launchPathArg;
|
||||
_launchApplicationId = launchApplicationId;
|
||||
_startFullscreen = startFullscreenArg;
|
||||
}
|
||||
|
||||
|
@ -219,7 +220,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
LibHacHorizonManager.InitializeBcatServer();
|
||||
LibHacHorizonManager.InitializeSystemClients();
|
||||
|
||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
|
||||
|
||||
// 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
|
||||
|
@ -314,7 +319,35 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
{
|
||||
_deferLoad = false;
|
||||
|
||||
await ViewModel.LoadApplication(_launchPath, _startFullscreen);
|
||||
if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
|
||||
{
|
||||
ApplicationData applicationData;
|
||||
|
||||
if (_launchApplicationId != null)
|
||||
{
|
||||
applicationData = applications.Find(application => application.IdString == _launchApplicationId);
|
||||
|
||||
if (applicationData != null)
|
||||
{
|
||||
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'.");
|
||||
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationData = applications[0];
|
||||
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'.");
|
||||
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -5,19 +5,18 @@ using Avalonia.Interactivity;
|
|||
using Avalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
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 TitleUpdateViewModel ViewModel;
|
||||
public readonly TitleUpdateViewModel ViewModel;
|
||||
|
||||
public TitleUpdateWindow()
|
||||
{
|
||||
|
@ -26,22 +25,22 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId);
|
||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = "",
|
||||
Content = new TitleUpdateWindow(virtualFileSystem, titleId),
|
||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
|
||||
Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
|
||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdString),
|
||||
};
|
||||
|
||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue