PartitionFileSystemExtensions.cs
1 using LibHac.Common; 2 using LibHac.Common.Keys; 3 using LibHac.Fs; 4 using LibHac.Fs.Fsa; 5 using LibHac.FsSystem; 6 using LibHac.Ncm; 7 using LibHac.Tools.Fs; 8 using LibHac.Tools.FsSystem; 9 using LibHac.Tools.FsSystem.NcaUtils; 10 using LibHac.Tools.Ncm; 11 using Ryujinx.Common.Configuration; 12 using Ryujinx.Common.Logging; 13 using Ryujinx.Common.Utilities; 14 using Ryujinx.HLE.FileSystem; 15 using System; 16 using System.Collections.Generic; 17 using System.IO; 18 using ContentType = LibHac.Ncm.ContentType; 19 20 namespace Ryujinx.HLE.Loaders.Processes.Extensions 21 { 22 public static class PartitionFileSystemExtensions 23 { 24 private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); 25 26 public static Dictionary<ulong, ContentMetaData> GetContentData(this IFileSystem partitionFileSystem, 27 ContentMetaType contentType, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel) 28 { 29 fileSystem.ImportTickets(partitionFileSystem); 30 31 var programs = new Dictionary<ulong, ContentMetaData>(); 32 33 foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca")) 34 { 35 Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, contentType); 36 37 if (cnmt == null) 38 { 39 continue; 40 } 41 42 ContentMetaData content = new(partitionFileSystem, cnmt); 43 44 if (content.Type != contentType) 45 { 46 continue; 47 } 48 49 programs.TryAdd(content.ApplicationId, content); 50 } 51 52 return programs; 53 } 54 55 internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage) 56 where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new() 57 where TFormat : IPartitionFileSystemFormat 58 where THeader : unmanaged, IPartitionFileSystemHeader 59 where TEntry : unmanaged, IPartitionFileSystemEntry 60 { 61 errorMessage = null; 62 63 // Load required NCAs. 64 Nca mainNca = null; 65 Nca patchNca = null; 66 Nca controlNca = null; 67 68 try 69 { 70 Dictionary<ulong, ContentMetaData> applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel); 71 72 if (applicationId == 0) 73 { 74 foreach ((ulong _, ContentMetaData content) in applications) 75 { 76 mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); 77 controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); 78 break; 79 } 80 } 81 else if (applications.TryGetValue(applicationId, out ContentMetaData content)) 82 { 83 mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); 84 controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); 85 } 86 87 ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); 88 } 89 catch (Exception ex) 90 { 91 errorMessage = $"Unable to load: {ex.Message}"; 92 93 return (false, ProcessResult.Failed); 94 } 95 96 if (mainNca != null) 97 { 98 if (mainNca.Header.ContentType != NcaContentType.Program) 99 { 100 errorMessage = "Selected NCA file is not a \"Program\" NCA"; 101 102 return (false, ProcessResult.Failed); 103 } 104 105 (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _); 106 107 if (updatePatchNca != null) 108 { 109 patchNca = updatePatchNca; 110 } 111 112 if (updateControlNca != null) 113 { 114 controlNca = updateControlNca; 115 } 116 117 // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here. 118 device.Configuration.ContentManager.ClearAocData(); 119 120 // Load DownloadableContents. 121 string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json"); 122 if (File.Exists(addOnContentMetadataPath)) 123 { 124 List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer); 125 126 foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) 127 { 128 foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) 129 { 130 if (File.Exists(downloadableContentContainer.ContainerPath)) 131 { 132 if (downloadableContentNca.Enabled) 133 { 134 device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); 135 } 136 } 137 else 138 { 139 Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); 140 } 141 } 142 } 143 } 144 145 return (true, mainNca.Load(device, patchNca, controlNca)); 146 } 147 148 errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\""; 149 150 return (false, ProcessResult.Failed); 151 } 152 153 public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path) 154 { 155 using var ncaFile = new UniqueRef<IFile>(); 156 157 fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); 158 159 return new Nca(keySet, ncaFile.Release().AsStorage()); 160 } 161 } 162 }