Revert "Add support for multi game XCIs (#5638)" (#5914)

This reverts commit 5c3cfb84c0.
This commit is contained in:
gdkchan 2023-11-11 23:35:30 -03:00 committed by GitHub
parent 6228331fd1
commit 51065d9129
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 819 additions and 1171 deletions

View file

@ -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();

View file

@ -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",

View file

@ -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;

View file

@ -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]);
});
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
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);
}
}
}

View file

@ -82,7 +82,7 @@
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"
Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Panel>

View file

@ -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

View file

@ -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;

View file

@ -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
{

View file

@ -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)
{

View file

@ -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)

View file

@ -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();

View file

@ -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)

View file

@ -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");
}
}

View file

@ -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}"

View file

@ -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();

View file

@ -97,7 +97,7 @@
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Label}" />
Text="{Binding FileName}" />
<TextBlock
Grid.Column="1"
Margin="10 0"

View file

@ -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>());

View file

@ -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

View file

@ -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>());