Move more DLC logic out of view model

This commit is contained in:
Jimmy Reichley 2024-08-16 23:26:25 -04:00
parent a90a6b2786
commit 7850a2b2aa
No known key found for this signature in database
GPG key ID: 67715DC5A329803C
7 changed files with 160 additions and 47 deletions

View file

@ -1,6 +1,6 @@
using DynamicData;
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSystem; using LibHac.FsSystem;
@ -19,9 +19,11 @@ using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Configuration.System; using Ryujinx.UI.Common.Configuration.System;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.Models; using Ryujinx.UI.Common.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -29,6 +31,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using ContentType = LibHac.Ncm.ContentType; using ContentType = LibHac.Ncm.ContentType;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers; using SpanHelpers = LibHac.Common.SpanHelpers;
using TimeSpan = System.TimeSpan; using TimeSpan = System.TimeSpan;
@ -43,6 +46,10 @@ namespace Ryujinx.UI.App.Common
public event EventHandler<TitleUpdateAddedEventArgs> TitleUpdateAdded; public event EventHandler<TitleUpdateAddedEventArgs> TitleUpdateAdded;
public event EventHandler<DownloadableContentAddedEventArgs> DownloadableContentAdded; public event EventHandler<DownloadableContentAddedEventArgs> DownloadableContentAdded;
public IObservableCache<ApplicationData, string> Applications;
public IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
public IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
private readonly byte[] _nspIcon; private readonly byte[] _nspIcon;
private readonly byte[] _xciIcon; private readonly byte[] _xciIcon;
private readonly byte[] _ncaIcon; private readonly byte[] _ncaIcon;
@ -52,6 +59,9 @@ namespace Ryujinx.UI.App.Common
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly IntegrityCheckLevel _checkLevel; private readonly IntegrityCheckLevel _checkLevel;
private CancellationTokenSource _cancellationToken; private CancellationTokenSource _cancellationToken;
private readonly SourceCache<ApplicationData, string> _applications = new(it => it.Path);
private readonly SourceCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> _titleUpdates = new(it => it.TitleUpdate);
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@ -60,6 +70,10 @@ namespace Ryujinx.UI.App.Common
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_checkLevel = checkLevel; _checkLevel = checkLevel;
Applications = _applications.AsObservableCache();
TitleUpdates = _titleUpdates.AsObservableCache();
DownloadableContents = _downloadableContents.AsObservableCache();
_nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png"); _nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png");
_xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png"); _xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png");
_ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png"); _ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png");
@ -105,7 +119,7 @@ namespace Ryujinx.UI.App.Common
return data; return data;
} }
/// <exception cref="MissingKeyException">The configured key set is missing a key.</exception> /// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
/// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception> /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
/// <exception cref="NotSupportedException">The NCA version is not supported.</exception> /// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
/// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception> /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
@ -181,7 +195,7 @@ namespace Ryujinx.UI.App.Common
return null; return null;
} }
/// <exception cref="MissingKeyException">The configured key set is missing a key.</exception> /// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
/// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception> /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
/// <exception cref="NotSupportedException">The NCA version is not supported.</exception> /// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
/// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception> /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
@ -638,6 +652,7 @@ namespace Ryujinx.UI.App.Common
int numApplicationsLoaded = 0; int numApplicationsLoaded = 0;
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
_applications.Clear();
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
List<string> applicationPaths = new(); List<string> applicationPaths = new();
@ -723,6 +738,14 @@ namespace Ryujinx.UI.App.Common
}); });
} }
_applications.Edit(it =>
{
foreach (var application in applications)
{
it.AddOrUpdate(application);
}
});
if (applications.Count > 1) if (applications.Count > 1)
{ {
numApplicationsFound += applications.Count - 1; numApplicationsFound += applications.Count - 1;
@ -755,9 +778,40 @@ namespace Ryujinx.UI.App.Common
} }
} }
public void LoadDownloadableContents(List<string> appDirs) public void LoadDownloadableContents()
{
_downloadableContents.Edit(it =>
{
it.Clear();
foreach (ApplicationData application in Applications.Items)
{
var res = DownloadableContentsHelper.LoadDownloadableContentsJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(res);
}
});
}
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
{
_downloadableContents.Edit(it =>
{
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase, dlcs);
it.Remove(it.Items.Where(item => item.Dlc.TitleIdBase == application.IdBase));
it.AddOrUpdate(dlcs);
});
}
// public void LoadTitleUpdates()
// {
//
// }
public void AutoLoadDownloadableContents(List<string> appDirs)
{ {
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
_downloadableContents.Clear();
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
List<string> applicationPaths = new(); List<string> applicationPaths = new();
@ -842,6 +896,14 @@ namespace Ryujinx.UI.App.Common
DownloadableContent = downloadableContent, DownloadableContent = downloadableContent,
}); });
} }
_downloadableContents.Edit(it =>
{
foreach (var downloadableContent in downloadableContents)
{
it.AddOrUpdate((downloadableContent, true));
}
});
} }
} }
} }
@ -850,12 +912,12 @@ namespace Ryujinx.UI.App.Common
_cancellationToken.Dispose(); _cancellationToken.Dispose();
_cancellationToken = null; _cancellationToken = null;
} }
} }
public void LoadTitleUpdates(List<string> appDirs) public void AutoLoadTitleUpdates(List<string> appDirs)
{ {
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
_titleUpdates.Clear();
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
List<string> applicationPaths = new(); List<string> applicationPaths = new();
@ -941,6 +1003,14 @@ namespace Ryujinx.UI.App.Common
TitleUpdate = titleUpdate, TitleUpdate = titleUpdate,
}); });
} }
_titleUpdates.Edit(it =>
{
foreach (var titleUpdate in titleUpdates)
{
it.AddOrUpdate((titleUpdate, false));
}
});
} }
} }
} }

View file

@ -20,9 +20,10 @@ namespace Ryujinx.UI.Common.Helper
{ {
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static List<(DownloadableContentModel, bool IsEnabled)> LoadSavedDownloadableContents(VirtualFileSystem vfs, ulong applicationIdBase) public static List<(DownloadableContentModel, bool IsEnabled)> LoadDownloadableContentsJson(VirtualFileSystem vfs, ulong applicationIdBase)
{ {
var downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("X16"), "dlc.json"); // _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "dlc.json");
var downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "dlc.json");
if (!File.Exists(downloadableContentJsonPath)) if (!File.Exists(downloadableContentJsonPath))
{ {
@ -42,6 +43,46 @@ namespace Ryujinx.UI.Common.Helper
} }
} }
public static void SaveDownloadableContentsJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
{
DownloadableContentContainer container = default;
List<DownloadableContentContainer> downloadableContentContainerList = new();
foreach ((DownloadableContentModel dlc, bool isEnabled) in dlcs)
{
if (container.ContainerPath != dlc.ContainerPath)
{
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
downloadableContentContainerList.Add(container);
}
container = new DownloadableContentContainer
{
ContainerPath = dlc.ContainerPath,
DownloadableContentNcaList = [],
};
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca
{
Enabled = isEnabled,
TitleId = dlc.TitleId,
FullPath = dlc.FullPath,
});
}
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
downloadableContentContainerList.Add(container);
}
// _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "dlc.json");
// var downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "dlc.json");
var downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "dlc.json");
JsonHelper.SerializeToFile(downloadableContentJsonPath, downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer);
}
private static List<(DownloadableContentModel, bool IsEnabled)> LoadDownloadableContents(VirtualFileSystem vfs, List<DownloadableContentContainer> downloadableContentContainers) private static List<(DownloadableContentModel, bool IsEnabled)> LoadDownloadableContents(VirtualFileSystem vfs, List<DownloadableContentContainer> downloadableContentContainers)
{ {
var result = new List<(DownloadableContentModel, bool IsEnabled)>(); var result = new List<(DownloadableContentModel, bool IsEnabled)>();

View file

@ -5,7 +5,7 @@ namespace Ryujinx.UI.Common.Models
public bool IsBundled { get; } = System.IO.Path.GetExtension(ContainerPath)?.ToLower() == ".xci"; public bool IsBundled { get; } = System.IO.Path.GetExtension(ContainerPath)?.ToLower() == ".xci";
public string FileName => System.IO.Path.GetFileName(ContainerPath); public string FileName => System.IO.Path.GetFileName(ContainerPath);
public string TitleIdStr => TitleId.ToString("X16"); public string TitleIdStr => TitleId.ToString("x16");
public ulong TitleIdBase => TitleId & ~0x1FFFUL; public ulong TitleIdBase => TitleId & ~0x1FFFUL;
} }
} }

View file

@ -4,7 +4,7 @@ namespace Ryujinx.UI.Common.Models
{ {
public bool IsBundled { get; } = System.IO.Path.GetExtension(Path)?.ToLower() == ".xci"; public bool IsBundled { get; } = System.IO.Path.GetExtension(Path)?.ToLower() == ".xci";
public string TitleIdStr => TitleId.ToString("X16"); public string TitleIdStr => TitleId.ToString("x16");
public ulong TitleIdBase => TitleId & ~0x1FFFUL; public ulong TitleIdBase => TitleId & ~0x1FFFUL;
} }
} }

View file

@ -56,6 +56,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscordRichPresence" /> <PackageReference Include="DiscordRichPresence" />
<PackageReference Include="DynamicData" />
<PackageReference Include="securifybv.ShellLink" /> <PackageReference Include="securifybv.ShellLink" />
</ItemGroup> </ItemGroup>

View file

@ -131,8 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadDownloadableContents() private void LoadDownloadableContents()
{ {
var savedDlc = DownloadableContentsHelper.LoadSavedDownloadableContents(_virtualFileSystem, _applicationData.IdBase); foreach ((DownloadableContentModel dlc, bool isEnabled) in _applicationLibrary.DownloadableContents.Items.Where(it => it.Dlc.TitleIdBase == _applicationData.IdBase))
foreach ((DownloadableContentModel dlc, bool isEnabled) in savedDlc)
{ {
DownloadableContents.Add(dlc); DownloadableContents.Add(dlc);
@ -300,40 +299,42 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Save() public void Save()
{ {
_downloadableContentContainerList.Clear(); var dlcs = DownloadableContents.Select(it => (it, SelectedDownloadableContents.Contains(it))).ToList();
_applicationLibrary.SaveDownloadableContentsForGame(_applicationData, dlcs);
// _downloadableContentContainerList.Clear();
DownloadableContentContainer container = default; // DownloadableContentContainer container = default;
//
foreach (DownloadableContentModel downloadableContent in DownloadableContents) // foreach (DownloadableContentModel downloadableContent in DownloadableContents)
{ // {
if (container.ContainerPath != downloadableContent.ContainerPath) // if (container.ContainerPath != downloadableContent.ContainerPath)
{ // {
if (!string.IsNullOrWhiteSpace(container.ContainerPath)) // if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{ // {
_downloadableContentContainerList.Add(container); // _downloadableContentContainerList.Add(container);
} // }
//
container = new DownloadableContentContainer // container = new DownloadableContentContainer
{ // {
ContainerPath = downloadableContent.ContainerPath, // ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>(), // DownloadableContentNcaList = new List<DownloadableContentNca>(),
}; // };
} // }
//
container.DownloadableContentNcaList.Add(new DownloadableContentNca // container.DownloadableContentNcaList.Add(new DownloadableContentNca
{ // {
Enabled = SelectedDownloadableContents.Contains(downloadableContent), // Enabled = SelectedDownloadableContents.Contains(downloadableContent),
TitleId = downloadableContent.TitleId, // TitleId = downloadableContent.TitleId,
FullPath = downloadableContent.FullPath, // FullPath = downloadableContent.FullPath,
}); // });
} // }
//
if (!string.IsNullOrWhiteSpace(container.ContainerPath)) // if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{ // {
_downloadableContentContainerList.Add(container); // _downloadableContentContainerList.Add(container);
} // }
//
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer); // JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer);
} }
} }

View file

@ -653,8 +653,8 @@ namespace Ryujinx.Ava.UI.Windows
{ {
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
TimeIt("games", () => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs)); TimeIt("games", () => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs));
TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates(ConfigurationState.Instance.UI.GameDirs)); // TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates(ConfigurationState.Instance.UI.GameDirs));
TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents(ConfigurationState.Instance.UI.GameDirs)); TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents());
_isLoading = false; _isLoading = false;
}) })