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.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -19,9 +19,11 @@ using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Configuration.System;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
@ -29,6 +31,7 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using ContentType = LibHac.Ncm.ContentType;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
using TimeSpan = System.TimeSpan;
@ -43,6 +46,10 @@ namespace Ryujinx.UI.App.Common
public event EventHandler<TitleUpdateAddedEventArgs> TitleUpdateAdded;
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[] _xciIcon;
private readonly byte[] _ncaIcon;
@ -52,6 +59,9 @@ namespace Ryujinx.UI.App.Common
private readonly VirtualFileSystem _virtualFileSystem;
private readonly IntegrityCheckLevel _checkLevel;
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());
@ -60,6 +70,10 @@ namespace Ryujinx.UI.App.Common
_virtualFileSystem = virtualFileSystem;
_checkLevel = checkLevel;
Applications = _applications.AsObservableCache();
TitleUpdates = _titleUpdates.AsObservableCache();
DownloadableContents = _downloadableContents.AsObservableCache();
_nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png");
_xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png");
_ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png");
@ -105,7 +119,7 @@ namespace Ryujinx.UI.App.Common
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="NotSupportedException">The NCA version is not supported.</exception>
/// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
@ -181,7 +195,7 @@ namespace Ryujinx.UI.App.Common
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="NotSupportedException">The NCA version is not supported.</exception>
/// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
@ -638,6 +652,7 @@ namespace Ryujinx.UI.App.Common
int numApplicationsLoaded = 0;
_cancellationToken = new CancellationTokenSource();
_applications.Clear();
// Builds the applications list with paths to found applications
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)
{
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();
_downloadableContents.Clear();
// Builds the applications list with paths to found applications
List<string> applicationPaths = new();
@ -842,6 +896,14 @@ namespace Ryujinx.UI.App.Common
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 = null;
}
}
public void LoadTitleUpdates(List<string> appDirs)
public void AutoLoadTitleUpdates(List<string> appDirs)
{
_cancellationToken = new CancellationTokenSource();
_titleUpdates.Clear();
// Builds the applications list with paths to found applications
List<string> applicationPaths = new();
@ -941,6 +1003,14 @@ namespace Ryujinx.UI.App.Common
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());
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))
{
@ -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)
{
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 string FileName => System.IO.Path.GetFileName(ContainerPath);
public string TitleIdStr => TitleId.ToString("X16");
public string TitleIdStr => TitleId.ToString("x16");
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 string TitleIdStr => TitleId.ToString("X16");
public string TitleIdStr => TitleId.ToString("x16");
public ulong TitleIdBase => TitleId & ~0x1FFFUL;
}
}

View file

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

View file

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

View file

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