Autoload DLC

This commit is contained in:
Jimmy Reichley 2024-08-17 17:10:27 -04:00
parent 867bc7021f
commit a381cea311
No known key found for this signature in database
GPG key ID: 67715DC5A329803C
3 changed files with 166 additions and 137 deletions

View file

@ -1,4 +1,5 @@
using DynamicData; using DynamicData;
using DynamicData.Kernel;
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
@ -813,6 +814,12 @@ namespace Ryujinx.UI.App.Common
}); });
} }
private void SaveDownloadableContentsForGame(ulong titleIdBase)
{
var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs);
}
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs) public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
{ {
_downloadableContents.Edit(it => _downloadableContents.Edit(it =>
@ -824,18 +831,12 @@ namespace Ryujinx.UI.App.Common
}); });
} }
// public void LoadTitleUpdates() public int AutoLoadDownloadableContents(List<string> appDirs)
// {
//
// }
public void AutoLoadDownloadableContents(List<string> appDirs)
{ {
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
_downloadableContents.Clear();
// Builds the applications list with paths to found applications List<string> dlcPaths = new();
List<string> applicationPaths = new(); int newDlcLoaded = 0;
try try
{ {
@ -843,7 +844,7 @@ namespace Ryujinx.UI.App.Common
{ {
if (_cancellationToken.Token.IsCancellationRequested) if (_cancellationToken.Token.IsCancellationRequested)
{ {
return; return newDlcLoaded;
} }
if (!Directory.Exists(appDir)) if (!Directory.Exists(appDir))
@ -867,16 +868,14 @@ namespace Ryujinx.UI.App.Common
{ {
return return
(Path.GetExtension(file).ToLower() is ".nsp" && (Path.GetExtension(file).ToLower() is ".nsp" &&
ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value);
(Path.GetExtension(file).ToLower() is ".xci" &&
ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value);
}); });
foreach (string app in files) foreach (string app in files)
{ {
if (_cancellationToken.Token.IsCancellationRequested) if (_cancellationToken.Token.IsCancellationRequested)
{ {
return; return newDlcLoaded;
} }
var fileInfo = new FileInfo(app); var fileInfo = new FileInfo(app);
@ -885,7 +884,7 @@ namespace Ryujinx.UI.App.Common
{ {
var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName; var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
applicationPaths.Add(fullPath); dlcPaths.Add(fullPath);
} }
catch (IOException exception) catch (IOException exception)
{ {
@ -901,31 +900,26 @@ namespace Ryujinx.UI.App.Common
} }
} }
// Loops through applications list, creating a struct and then firing an event containing the struct for each application var appIdLookup = Applications.Items.Select(it => it.IdBase).ToHashSet();
foreach (string applicationPath in applicationPaths)
foreach (string dlcPath in dlcPaths)
{ {
if (_cancellationToken.Token.IsCancellationRequested) if (_cancellationToken.Token.IsCancellationRequested)
{ {
return; return newDlcLoaded;
} }
if (TryGetDownloadableContentFromFile(applicationPath, out List<DownloadableContentModel> downloadableContents)) if (TryGetDownloadableContentFromFile(dlcPath, out var foundDlcs))
{ {
foreach (var downloadableContent in downloadableContents) foreach (var dlc in foundDlcs.Where(it => appIdLookup.Contains(it.TitleIdBase)))
{ {
OnDownloadableContentAdded(new DownloadableContentAddedEventArgs if (!DownloadableContents.Items.Any(it => it.Dlc == dlc))
{ {
DownloadableContent = downloadableContent, _downloadableContents.AddOrUpdate((dlc, true));
}); SaveDownloadableContentsForGame(dlc.TitleIdBase);
} newDlcLoaded++;
_downloadableContents.Edit(it =>
{
foreach (var downloadableContent in downloadableContents)
{
it.AddOrUpdate((downloadableContent, true));
} }
}); }
} }
} }
} }
@ -934,114 +928,117 @@ namespace Ryujinx.UI.App.Common
_cancellationToken.Dispose(); _cancellationToken.Dispose();
_cancellationToken = null; _cancellationToken = null;
} }
return newDlcLoaded;
} }
public void AutoLoadTitleUpdates(List<string> appDirs) public void AutoLoadTitleUpdates(List<string> appDirs)
{ {
_cancellationToken = new CancellationTokenSource(); return;
_titleUpdates.Clear(); // _cancellationToken = new CancellationTokenSource();
// _titleUpdates.Clear();
// Builds the applications list with paths to found applications //
List<string> applicationPaths = new(); // // Builds the applications list with paths to found applications
// List<string> applicationPaths = new();
try //
{ // try
foreach (string appDir in appDirs) // {
{ // foreach (string appDir in appDirs)
if (_cancellationToken.Token.IsCancellationRequested) // {
{ // if (_cancellationToken.Token.IsCancellationRequested)
return; // {
} // return;
// }
if (!Directory.Exists(appDir)) //
{ // if (!Directory.Exists(appDir))
Logger.Warning?.Print(LogClass.Application, // {
$"The specified game directory \"{appDir}\" does not exist."); // Logger.Warning?.Print(LogClass.Application,
// $"The specified game directory \"{appDir}\" does not exist.");
continue; //
} // continue;
// }
try //
{ // try
EnumerationOptions options = new() // {
{ // EnumerationOptions options = new()
RecurseSubdirectories = true, // {
IgnoreInaccessible = false, // RecurseSubdirectories = true,
}; // IgnoreInaccessible = false,
// };
IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options) //
.Where(file => // IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options)
{ // .Where(file =>
return // {
(Path.GetExtension(file).ToLower() is ".nsp" && // return
ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || // (Path.GetExtension(file).ToLower() is ".nsp" &&
(Path.GetExtension(file).ToLower() is ".xci" && // ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) ||
ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value); // (Path.GetExtension(file).ToLower() is ".xci" &&
}); // ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value);
// });
foreach (string app in files) //
{ // foreach (string app in files)
if (_cancellationToken.Token.IsCancellationRequested) // {
{ // if (_cancellationToken.Token.IsCancellationRequested)
return; // {
} // return;
// }
var fileInfo = new FileInfo(app); //
// var fileInfo = new FileInfo(app);
try //
{ // try
var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? // {
fileInfo.FullName; // var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ??
// fileInfo.FullName;
applicationPaths.Add(fullPath); //
} // applicationPaths.Add(fullPath);
catch (IOException exception) // }
{ // catch (IOException exception)
Logger.Warning?.Print(LogClass.Application, // {
$"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); // Logger.Warning?.Print(LogClass.Application,
} // $"Failed to resolve the full path to file: \"{app}\" Error: {exception}");
} // }
} // }
catch (UnauthorizedAccessException) // }
{ // catch (UnauthorizedAccessException)
Logger.Warning?.Print(LogClass.Application, // {
$"Failed to get access to directory: \"{appDir}\""); // Logger.Warning?.Print(LogClass.Application,
} // $"Failed to get access to directory: \"{appDir}\"");
} // }
// }
// Loops through applications list, creating a struct and then firing an event containing the struct for each application //
foreach (string applicationPath in applicationPaths) // // Loops through applications list, creating a struct and then firing an event containing the struct for each application
{ // foreach (string applicationPath in applicationPaths)
if (_cancellationToken.Token.IsCancellationRequested) // {
{ // if (_cancellationToken.Token.IsCancellationRequested)
return; // {
} // return;
// }
if (TryGetTitleUpdatesFromFile(applicationPath, out List<TitleUpdateModel> titleUpdates)) //
{ // if (TryGetTitleUpdatesFromFile(applicationPath, out List<TitleUpdateModel> titleUpdates))
foreach (var titleUpdate in titleUpdates) // {
{ // foreach (var titleUpdate in titleUpdates)
OnTitleUpdateAdded(new TitleUpdateAddedEventArgs() // {
{ // OnTitleUpdateAdded(new TitleUpdateAddedEventArgs()
TitleUpdate = titleUpdate, // {
}); // TitleUpdate = titleUpdate,
} // });
// }
_titleUpdates.Edit(it => //
{ // _titleUpdates.Edit(it =>
foreach (var titleUpdate in titleUpdates) // {
{ // foreach (var titleUpdate in titleUpdates)
it.AddOrUpdate((titleUpdate, false)); // {
} // it.AddOrUpdate((titleUpdate, false));
}); // }
} // });
} // }
} // }
finally // }
{ // finally
_cancellationToken.Dispose(); // {
_cancellationToken = null; // _cancellationToken.Dispose();
} // _cancellationToken = null;
// }
} }
protected void OnApplicationAdded(ApplicationAddedEventArgs e) protected void OnApplicationAdded(ApplicationAddedEventArgs e)

View file

@ -709,11 +709,15 @@
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
"ModWindowTitle": "Manage Mods for {0} ({1})", "ModWindowTitle": "Manage Mods for {0} ({1})",
"UpdateWindowTitle": "Title Update Manager", "UpdateWindowTitle": "Title Update Manager",
"UpdateWindowUpdateAddedMessage": "{0} new update(s) added",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"BuildId": "BuildId:", "BuildId": "BuildId:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} Downloadable Content(s)", "DlcWindowHeading": "{0} Downloadable Content(s)",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadUpdateAddedMessage": "{0} new update(s) added",
"AutoloadDlcAndUpdateAddedMessage": "{0} new downloadable content(s) and {0} new update(s) added",
"ModWindowHeading": "{0} Mod(s)", "ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel", "Cancel": "Cancel",

View file

@ -485,8 +485,6 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ApplicationLibrary.DownloadableContentAdded += ApplicationLibrary_DownloadableContentAdded;
ApplicationLibrary.TitleUpdateAdded += ApplicationLibrary_TitleUpdateAdded;
ViewModel.RefreshFirmwareStatus(); ViewModel.RefreshFirmwareStatus();
@ -655,6 +653,14 @@ namespace Ryujinx.Ava.UI.Windows
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()); TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents());
// TODO(jpr): conditional
var dlcLoaded = 0;
TimeIt("AUTO DLC", () => dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(ConfigurationState.Instance.UI.GameDirs));
if (dlcLoaded > 0)
{
ShowNewContentAddedDialog(dlcLoaded, 0);
}
_isLoading = false; _isLoading = false;
}) })
@ -673,5 +679,27 @@ namespace Ryujinx.Ava.UI.Windows
var elapsedMs = watch.ElapsedMilliseconds; var elapsedMs = watch.ElapsedMilliseconds;
Console.WriteLine("[{0}] {1} ms", tag, elapsedMs); Console.WriteLine("[{0}] {1} ms", tag, elapsedMs);
} }
private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
{
var msg = "";
if (numDlcAdded > 0 && numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded);
} else if (numDlcAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded);
} else if (numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded);
}
return msg == "" ? Task.CompletedTask : Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
});
}
} }
} }