forked from ryzendew/Ryujinx
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from Ryujinx-NX/#7205-Auto-add-DLC-and-updates
#7205 auto add dlc and updates
- Loading branch information
Showing
30 changed files
with
1,505 additions
and
455 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
src/Ryujinx.UI.Common/Helper/DownloadableContentsHelper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using LibHac.Common; | ||
using LibHac.Fs; | ||
using LibHac.Fs.Fsa; | ||
using LibHac.Tools.FsSystem; | ||
using LibHac.Tools.FsSystem.NcaUtils; | ||
using Ryujinx.Common.Configuration; | ||
using Ryujinx.Common.Logging; | ||
using Ryujinx.Common.Utilities; | ||
using Ryujinx.HLE.FileSystem; | ||
using Ryujinx.HLE.Utilities; | ||
using Ryujinx.UI.Common.Models; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using Path = System.IO.Path; | ||
|
||
namespace Ryujinx.UI.Common.Helper | ||
{ | ||
public static class DownloadableContentsHelper | ||
{ | ||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); | ||
|
||
public static List<(DownloadableContentModel, bool IsEnabled)> LoadDownloadableContentsJson(VirtualFileSystem vfs, ulong applicationIdBase) | ||
{ | ||
var downloadableContentJsonPath = PathToGameDLCJson(applicationIdBase); | ||
|
||
if (!File.Exists(downloadableContentJsonPath)) | ||
{ | ||
return []; | ||
} | ||
|
||
try | ||
{ | ||
var downloadableContentContainerList = JsonHelper.DeserializeFromFile(downloadableContentJsonPath, | ||
_serializerContext.ListDownloadableContentContainer); | ||
return LoadDownloadableContents(vfs, downloadableContentContainerList); | ||
} | ||
catch | ||
{ | ||
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize."); | ||
return []; | ||
} | ||
} | ||
|
||
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); | ||
} | ||
|
||
var downloadableContentJsonPath = PathToGameDLCJson(applicationIdBase); | ||
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)>(); | ||
|
||
foreach (DownloadableContentContainer downloadableContentContainer in downloadableContentContainers) | ||
{ | ||
if (!File.Exists(downloadableContentContainer.ContainerPath)) | ||
{ | ||
continue; | ||
} | ||
|
||
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(downloadableContentContainer.ContainerPath, vfs); | ||
|
||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) | ||
{ | ||
using UniqueRef<IFile> ncaFile = new(); | ||
|
||
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
|
||
Nca nca = TryOpenNca(vfs, ncaFile.Get.AsStorage()); | ||
if (nca == null) | ||
{ | ||
continue; | ||
} | ||
|
||
var content = new DownloadableContentModel(nca.Header.TitleId, | ||
downloadableContentContainer.ContainerPath, | ||
downloadableContentNca.FullPath); | ||
|
||
result.Add((content, downloadableContentNca.Enabled)); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static Nca TryOpenNca(VirtualFileSystem vfs, IStorage ncaStorage) | ||
{ | ||
try | ||
{ | ||
return new Nca(vfs.KeySet, ncaStorage); | ||
} | ||
catch (Exception) { } | ||
|
||
return null; | ||
} | ||
|
||
private static string PathToGameDLCJson(ulong applicationIdBase) | ||
{ | ||
return Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "dlc.json"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
using LibHac.Common; | ||
using LibHac.Common.Keys; | ||
using LibHac.Fs; | ||
using LibHac.Fs.Fsa; | ||
using LibHac.Ncm; | ||
using LibHac.Ns; | ||
using LibHac.Tools.FsSystem; | ||
using LibHac.Tools.FsSystem.NcaUtils; | ||
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.Common.Configuration; | ||
using Ryujinx.UI.Common.Models; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using ContentType = LibHac.Ncm.ContentType; | ||
using Path = System.IO.Path; | ||
using SpanHelpers = LibHac.Common.SpanHelpers; | ||
using TitleUpdateMetadata = Ryujinx.Common.Configuration.TitleUpdateMetadata; | ||
|
||
namespace Ryujinx.UI.Common.Helper | ||
{ | ||
public static class TitleUpdatesHelper | ||
{ | ||
private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); | ||
|
||
public static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase) | ||
{ | ||
var titleUpdatesJsonPath = PathToGameUpdatesJson(applicationIdBase); | ||
|
||
if (!File.Exists(titleUpdatesJsonPath)) | ||
{ | ||
return []; | ||
} | ||
|
||
try | ||
{ | ||
var titleUpdateWindowData = JsonHelper.DeserializeFromFile(titleUpdatesJsonPath, _serializerContext.TitleUpdateMetadata); | ||
return LoadTitleUpdates(vfs, titleUpdateWindowData, applicationIdBase); | ||
} | ||
catch | ||
{ | ||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {applicationIdBase:x16} at {titleUpdatesJsonPath}"); | ||
return []; | ||
} | ||
} | ||
|
||
public static void SaveTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(TitleUpdateModel, bool IsSelected)> updates) | ||
{ | ||
var titleUpdateWindowData = new TitleUpdateMetadata | ||
{ | ||
Selected = "", | ||
Paths = [], | ||
}; | ||
|
||
foreach ((TitleUpdateModel update, bool isSelected) in updates) | ||
{ | ||
titleUpdateWindowData.Paths.Add(update.Path); | ||
if (isSelected) | ||
{ | ||
if (!string.IsNullOrEmpty(titleUpdateWindowData.Selected)) | ||
{ | ||
Logger.Error?.Print(LogClass.Application, | ||
$"Tried to save two updates as 'IsSelected' for {applicationIdBase:x16}"); | ||
return; | ||
} | ||
|
||
titleUpdateWindowData.Selected = update.Path; | ||
} | ||
} | ||
|
||
var titleUpdatesJsonPath = PathToGameUpdatesJson(applicationIdBase); | ||
JsonHelper.SerializeToFile(titleUpdatesJsonPath, titleUpdateWindowData, _serializerContext.TitleUpdateMetadata); | ||
} | ||
|
||
private static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdates(VirtualFileSystem vfs, TitleUpdateMetadata titleUpdateMetadata, ulong applicationIdBase) | ||
{ | ||
var result = new List<(TitleUpdateModel, bool IsSelected)>(); | ||
|
||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks | ||
? IntegrityCheckLevel.ErrorOnInvalid | ||
: IntegrityCheckLevel.None; | ||
|
||
foreach (string path in titleUpdateMetadata.Paths) | ||
{ | ||
if (!File.Exists(path)) | ||
{ | ||
continue; | ||
} | ||
|
||
try | ||
{ | ||
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, vfs); | ||
|
||
Dictionary<ulong, ContentMetaData> updates = | ||
pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel); | ||
|
||
Nca patchNca = null; | ||
Nca controlNca = null; | ||
|
||
if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content)) | ||
{ | ||
continue; | ||
} | ||
|
||
patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program); | ||
controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control); | ||
|
||
if (controlNca == null || patchNca == null) | ||
{ | ||
continue; | ||
} | ||
|
||
ApplicationControlProperty controlData = 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(); | ||
|
||
var displayVersion = controlData.DisplayVersionString.ToString(); | ||
var update = new TitleUpdateModel(content.ApplicationId, content.Version.Version, | ||
displayVersion, path); | ||
|
||
result.Add((update, path == titleUpdateMetadata.Selected)); | ||
} | ||
catch (MissingKeyException exception) | ||
{ | ||
Logger.Warning?.Print(LogClass.Application, | ||
$"Your key set is missing a key with the name: {exception.Name}"); | ||
} | ||
catch (InvalidDataException) | ||
{ | ||
Logger.Warning?.Print(LogClass.Application, | ||
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {path}"); | ||
} | ||
catch (IOException exception) | ||
{ | ||
Logger.Warning?.Print(LogClass.Application, exception.Message); | ||
} | ||
catch (Exception exception) | ||
{ | ||
Logger.Warning?.Print(LogClass.Application, | ||
$"The file encountered was not of a valid type. File: '{path}' Error: {exception}"); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static string PathToGameUpdatesJson(ulong applicationIdBase) | ||
{ | ||
return Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json"); | ||
} | ||
} | ||
} |
Oops, something went wrong.