diff --git a/src/LibHac/Cnmt.cs b/src/LibHac/Cnmt.cs index d5500bf0..97e553ac 100644 --- a/src/LibHac/Cnmt.cs +++ b/src/LibHac/Cnmt.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using LibHac.IO.NcaUtils; namespace LibHac { diff --git a/src/LibHac/IO/BucketTree.cs b/src/LibHac/IO/BucketTree.cs index 914c1ee3..9e533284 100644 --- a/src/LibHac/IO/BucketTree.cs +++ b/src/LibHac/IO/BucketTree.cs @@ -48,7 +48,7 @@ namespace LibHac.IO public string Magic; public int Version; public int NumEntries; - public int Field1C; + public int FieldC; public BucketTreeHeader(IStorage storage) { @@ -57,7 +57,7 @@ namespace LibHac.IO Magic = reader.ReadAscii(4); Version = reader.ReadInt32(); NumEntries = reader.ReadInt32(); - Field1C = reader.ReadInt32(); + FieldC = reader.ReadInt32(); } } diff --git a/src/LibHac/Nca.cs b/src/LibHac/IO/NcaUtils/Nca.cs similarity index 60% rename from src/LibHac/Nca.cs rename to src/LibHac/IO/NcaUtils/Nca.cs index 341ee4c1..d2cf3084 100644 --- a/src/LibHac/Nca.cs +++ b/src/LibHac/IO/NcaUtils/Nca.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LibHac.IO; using LibHac.IO.RomFs; -namespace LibHac +namespace LibHac.IO.NcaUtils { public class Nca : IDisposable { @@ -89,13 +88,27 @@ namespace LibHac return sect.Header.EncryptionType == NcaEncryptionType.None || !IsMissingTitleKey && string.IsNullOrWhiteSpace(MissingKeyName); } - private IStorage OpenRawSection(int index, bool leaveOpen) + private IStorage OpenEncryptedStorage(int index) { if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index)); NcaSection sect = Sections[index]; - if (sect == null) return null; + if (sect == null) throw new ArgumentOutOfRangeException(nameof(index), "Section is empty"); + long offset = sect.Offset; + long size = sect.Size; + + if (!Util.IsSubRange(offset, size, BaseStorage.Length)) + { + throw new InvalidDataException( + $"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{BaseStorage.Length:x})."); + } + + return BaseStorage.Slice(offset, size); + } + + private IStorage OpenDecryptedStorage(IStorage baseStorage, NcaSection sect) + { if (sect.Header.EncryptionType != NcaEncryptionType.None) { if (IsMissingTitleKey) @@ -109,118 +122,113 @@ namespace LibHac } } - long offset = sect.Offset; - long size = sect.Size; - - // todo - //if (!Util.IsSubRange(offset, size, StreamSource.Length)) - //{ - // throw new InvalidDataException( - // $"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{StreamSource.Length:x})."); - //} - - IStorage rawStorage = BaseStorage.Slice(offset, size, leaveOpen); - switch (sect.Header.EncryptionType) { case NcaEncryptionType.None: - return rawStorage; + return baseStorage; case NcaEncryptionType.XTS: throw new NotImplementedException("NCA sections using XTS are not supported"); case NcaEncryptionType.AesCtr: - return new CachedStorage(new Aes128CtrStorage(rawStorage, DecryptedKeys[2], offset, sect.Header.Ctr, leaveOpen), 0x4000, 4, leaveOpen); + return new CachedStorage(new Aes128CtrStorage(baseStorage, DecryptedKeys[2], sect.Offset, sect.Header.Ctr, true), 0x4000, 4, true); case NcaEncryptionType.AesCtrEx: BktrPatchInfo info = sect.Header.BktrInfo; long bktrOffset = info.RelocationHeader.Offset; - long bktrSize = size - bktrOffset; + long bktrSize = sect.Size - bktrOffset; long dataSize = info.RelocationHeader.Offset; IStorage bucketTreeHeader = new MemoryStorage(sect.Header.BktrInfo.EncryptionHeader.Header); - IStorage bucketTreeData = new CachedStorage(new Aes128CtrStorage(rawStorage.Slice(bktrOffset, bktrSize, leaveOpen), DecryptedKeys[2], bktrOffset + offset, sect.Header.Ctr, leaveOpen), 4, leaveOpen); + IStorage bucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), DecryptedKeys[2], bktrOffset + sect.Offset, sect.Header.Ctr, true), 4, true); IStorage encryptionBucketTreeData = bucketTreeData.Slice(info.EncryptionHeader.Offset - bktrOffset); - IStorage decStorage = new Aes128CtrExStorage(rawStorage.Slice(0, dataSize, leaveOpen), bucketTreeHeader, encryptionBucketTreeData, DecryptedKeys[2], offset, sect.Header.Ctr, leaveOpen); - decStorage = new CachedStorage(decStorage, 0x4000, 4, leaveOpen); + IStorage decStorage = new Aes128CtrExStorage(baseStorage.Slice(0, dataSize), bucketTreeHeader, encryptionBucketTreeData, DecryptedKeys[2], sect.Offset, sect.Header.Ctr, true); + decStorage = new CachedStorage(decStorage, 0x4000, 4, true); - return new ConcatenationStorage(new[] { decStorage, bucketTreeData }, leaveOpen); + return new ConcatenationStorage(new[] { decStorage, bucketTreeData }, true); default: throw new ArgumentOutOfRangeException(); } } - /// - /// Opens one of the sections in the current . - /// - /// The index of the NCA section to open. Valid indexes are 0-3. - /// to open the raw section with hash metadata. - /// The level of integrity checks to be performed when reading the section. - /// Always if is . - /// to leave the storage open after the object is disposed; otherwise, . - /// A that provides access to the specified section. if the section does not exist. - /// The specified is outside the valid range. - public IStorage OpenSection(int index, bool raw, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + public IStorage OpenRawStorage(int index) { - IStorage rawStorage = OpenRawSection(index, leaveOpen); + IStorage encryptedStorage = OpenEncryptedStorage(index); + IStorage decryptedStorage = OpenDecryptedStorage(encryptedStorage, Sections[index]); + + return decryptedStorage; + } + + public IStorage OpenRawStorage(NcaSectionType type) + { + return OpenRawStorage(GetSectionIndexFromType(type)); + } + + public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage rawStorage = OpenRawStorage(index); NcaSection sect = Sections[index]; NcaFsHeader header = sect.Header; + // todo don't assume that ctr ex means it's a patch if (header.EncryptionType == NcaEncryptionType.AesCtrEx) { - if (raw && BaseNca == null) return rawStorage; - - BktrHeader bktrInfo = header.BktrInfo.RelocationHeader; - IStorage patchStorage = rawStorage.Slice(0, bktrInfo.Offset, leaveOpen); - - if (BaseNca == null) return patchStorage; - - IStorage baseStorage = BaseNca.OpenSection(ProgramPartitionType.Data, true, IntegrityCheckLevel.None, leaveOpen); - IStorage bktrHeader = new MemoryStorage(bktrInfo.Header); - IStorage bktrData = rawStorage.Slice(bktrInfo.Offset, bktrInfo.Size, leaveOpen); - - rawStorage = new IndirectStorage(bktrHeader, bktrData, leaveOpen, baseStorage, patchStorage); + return rawStorage.Slice(0, header.BktrInfo.RelocationHeader.Offset); } - if (raw || rawStorage == null) return rawStorage; - switch (header.HashType) { case NcaHashType.Sha256: - return InitIvfcForPartitionfs(header.Sha256Info, rawStorage, integrityCheckLevel, leaveOpen); + return InitIvfcForPartitionfs(header.Sha256Info, rawStorage, integrityCheckLevel, true); case NcaHashType.Ivfc: return new HierarchicalIntegrityVerificationStorage(header.IvfcInfo, new MemoryStorage(header.IvfcInfo.MasterHash), rawStorage, - IntegrityStorageType.RomFs, integrityCheckLevel, leaveOpen); + IntegrityStorageType.RomFs, integrityCheckLevel, true); default: throw new ArgumentOutOfRangeException(); } } - /// - /// Opens one of the sections in the current as a - /// Only works with sections that have a of or . - /// - /// The index of the NCA section to open. Valid indexes are 0-3. - /// The level of integrity checks to be performed when reading the section. - /// to leave the storage open after the object is disposed; otherwise, . - /// A that provides access to the specified section. if the section does not exist, - /// or is has no hash metadata. - /// The specified is outside the valid range. - public HierarchicalIntegrityVerificationStorage OpenHashedSection(int index, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) => - OpenSection(index, false, integrityCheckLevel, leaveOpen) as HierarchicalIntegrityVerificationStorage; + public IStorage OpenStorage(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenStorage(GetSectionIndexFromType(type), integrityCheckLevel); + } - /// - /// Opens one of the sections in the current . For use with type NCAs. - /// - /// The type of section to open. - /// to open the raw section with hash metadata. - /// The level of integrity checks to be performed when reading the section. - /// Always if is . - /// to leave the storage open after the object is disposed; otherwise, . - /// A that provides access to the specified section. if the section does not exist. - /// The specified is outside the valid range. - public IStorage OpenSection(ProgramPartitionType type, bool raw, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) => - OpenSection((int)type, raw, integrityCheckLevel, leaveOpen); + public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage storage = OpenStorage(index, integrityCheckLevel); + + switch (Sections[index].Header.Type) + { + case SectionType.Pfs0: + return new PartitionFileSystem(storage); + case SectionType.Romfs: + return new RomFsFileSystem(storage); + case SectionType.Bktr: + // todo Possibly check if a patch completely replaces the original + throw new InvalidOperationException("Cannot open a patched section without the original"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + public IFileSystem OpenFileSystem(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenFileSystem(GetSectionIndexFromType(type), integrityCheckLevel); + } + + private int GetSectionIndexFromType(NcaSectionType type) + { + ContentType contentType = Header.ContentType; + + switch (type) + { + case NcaSectionType.Code when contentType == ContentType.Program: return 0; + case NcaSectionType.Data when contentType == ContentType.Program: return 1; + case NcaSectionType.Logo when contentType == ContentType.Program: return 2; + case NcaSectionType.Data: return 0; + default: throw new ArgumentOutOfRangeException(nameof(type), "NCA does not contain this section type."); + } + } private static HierarchicalIntegrityVerificationStorage InitIvfcForPartitionfs(Sha256Info sb, IStorage pfsStorage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) @@ -257,24 +265,18 @@ namespace LibHac return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); } - public IFileSystem OpenSectionFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) + public bool SectionExists(int index) { - if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index)); - NcaFsHeader header = Sections[index].Header; + if (index < 0 || index > 3) return false; - IStorage storage = OpenSection(index, false, integrityCheckLevel, true); + return Sections[index] != null; + } - switch (header.Type) - { - case SectionType.Pfs0: - return new PartitionFileSystem(storage); - case SectionType.Romfs: - return new RomFsFileSystem(storage); - case SectionType.Bktr: - return new RomFsFileSystem(storage); - default: - throw new ArgumentOutOfRangeException(); - } + public bool SectionIsDecryptable(int index) + { + if (!SectionExists(index)) return false; + + return Sections[index].Header.EncryptionType == NcaEncryptionType.None || !IsMissingTitleKey && string.IsNullOrWhiteSpace(MissingKeyName); } /// @@ -299,11 +301,11 @@ namespace LibHac { if (Header.ContentType != ContentType.Program) return; - var pfs = new PartitionFileSystem(OpenSection(ProgramPartitionType.Code, false, IntegrityCheckLevel.ErrorOnInvalid, true)); + IFileSystem pfs = OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid); if (!pfs.FileExists("main.npdm")) return; - IStorage npdmStorage = pfs.OpenFile("main.npdm", OpenMode.Read).AsStorage(); + IFile npdmStorage = pfs.OpenFile("main.npdm", OpenMode.Read); Npdm = new Npdm.NpdmBinary(npdmStorage.AsStream(), Keyset); @@ -316,7 +318,7 @@ namespace LibHac foreach (NcaSection section in Sections.Where(x => x != null).OrderBy(x => x.Offset)) { - list.Add(OpenRawSection(section.SectionNum, true)); + list.Add(OpenRawStorage(section.SectionNum)); } return new ConcatenationStorage(list, true); @@ -425,7 +427,7 @@ namespace LibHac break; } - IStorage storage = OpenSection(index, true, IntegrityCheckLevel.None, true); + IStorage storage = OpenRawStorage(index); var hashTable = new byte[size]; storage.Read(hashTable, offset); @@ -496,88 +498,4 @@ namespace LibHac return hash; } } - - public static class NcaExtensions - { - public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) - { - if (index < 0 || index > 3) throw new IndexOutOfRangeException(); - if (nca.Sections[index] == null) return; - - IStorage storage = nca.OpenSection(index, raw, integrityCheckLevel, true); - string dir = Path.GetDirectoryName(filename); - if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir); - - using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite)) - { - storage.CopyToStream(outFile, storage.Length, logger); - } - } - - public static void ExtractSection(this Nca nca, int index, string outputDir, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) - { - if (index < 0 || index > 3) throw new IndexOutOfRangeException(); - if (nca.Sections[index] == null) return; - - NcaSection section = nca.Sections[index]; - IStorage storage = nca.OpenSection(index, false, integrityCheckLevel, true); - - switch (section.Type) - { - case SectionType.Invalid: - break; - case SectionType.Pfs0: - var pfs0 = new PartitionFileSystem(storage); - pfs0.Extract(outputDir, logger); - break; - case SectionType.Romfs: - var romfs = new RomFsFileSystem(storage); - romfs.Extract(outputDir, logger); - break; - case SectionType.Bktr: - break; - } - } - - public static Validity VerifyNca(this Nca nca, IProgressReport logger = null, bool quiet = false) - { - for (int i = 0; i < 3; i++) - { - if (nca.Sections[i] != null) - { - Validity sectionValidity = VerifySection(nca, i, logger, quiet); - - if (sectionValidity == Validity.Invalid) return Validity.Invalid; - } - } - - return Validity.Valid; - } - - public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false) - { - if (nca.Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index)); - - NcaSection sect = nca.Sections[index]; - NcaHashType hashType = sect.Header.HashType; - if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; - - HierarchicalIntegrityVerificationStorage stream = nca.OpenHashedSection(index, IntegrityCheckLevel.IgnoreOnInvalid, true); - if (stream == null) return Validity.Unchecked; - - if (!quiet) logger?.LogMessage($"Verifying section {index}..."); - Validity validity = stream.Validate(true, logger); - - if (hashType == NcaHashType.Ivfc) - { - stream.SetLevelValidities(sect.Header.IvfcInfo); - } - else if (hashType == NcaHashType.Sha256) - { - sect.Header.Sha256Info.HashValidity = validity; - } - - return validity; - } - } } diff --git a/src/LibHac/IO/NcaUtils/NcaExtensions.cs b/src/LibHac/IO/NcaUtils/NcaExtensions.cs new file mode 100644 index 00000000..858ae1bc --- /dev/null +++ b/src/LibHac/IO/NcaUtils/NcaExtensions.cs @@ -0,0 +1,76 @@ +using System; + +namespace LibHac.IO.NcaUtils +{ + public static class NcaExtensions + { + public static IStorage OpenStorage(this Nca nca, int index, IntegrityCheckLevel integrityCheckLevel, bool openRaw) + { + if (openRaw) return nca.OpenRawStorage(index); + return nca.OpenStorage(index, integrityCheckLevel); + } + + public static IStorage OpenStorage(this Nca nca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel, bool openRaw) + { + if (openRaw) return nca.OpenRawStorage(type); + return nca.OpenStorage(type, integrityCheckLevel); + } + + public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + nca.OpenStorage(index, integrityCheckLevel, raw) + .WriteAllBytes(filename, logger); + } + + public static void ExtractSection(this Nca nca, int index, string outputDir, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + if (index < 0 || index > 3) throw new IndexOutOfRangeException(); + if (!nca.SectionIsDecryptable(index)) return; + + IFileSystem fs = nca.OpenFileSystem(index, integrityCheckLevel); + fs.Extract(outputDir, logger); + } + + public static Validity VerifyNca(this Nca nca, IProgressReport logger = null, bool quiet = false) + { + for (int i = 0; i < 3; i++) + { + if (nca.Sections[i] != null) + { + Validity sectionValidity = VerifySection(nca, i, logger, quiet); + + if (sectionValidity == Validity.Invalid) return Validity.Invalid; + } + } + + return Validity.Valid; + } + + public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false) + { + if (nca.Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index)); + + NcaSection sect = nca.Sections[index]; + NcaHashType hashType = sect.Header.HashType; + if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; + + var stream = nca.OpenStorage(index, IntegrityCheckLevel.IgnoreOnInvalid, false) as HierarchicalIntegrityVerificationStorage; + if (stream == null) return Validity.Unchecked; + + if (!quiet) logger?.LogMessage($"Verifying section {index}..."); + Validity validity = stream.Validate(true, logger); + + if (hashType == NcaHashType.Ivfc) + { + stream.SetLevelValidities(sect.Header.IvfcInfo); + } + else if (hashType == NcaHashType.Sha256) + { + sect.Header.Sha256Info.HashValidity = validity; + } + + return validity; + } + } +} diff --git a/src/LibHac/IO/NcaUtils/NcaFsHeader.cs b/src/LibHac/IO/NcaUtils/NcaFsHeader.cs new file mode 100644 index 00000000..23e7fddf --- /dev/null +++ b/src/LibHac/IO/NcaUtils/NcaFsHeader.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Linq; + +namespace LibHac.IO.NcaUtils +{ + public class NcaFsHeader + { + public short Version; + public NcaFormatType FormatType; + public NcaHashType HashType; + public NcaEncryptionType EncryptionType; + public SectionType Type; + + public IvfcHeader IvfcInfo; + public Sha256Info Sha256Info; + public BktrPatchInfo BktrInfo; + + public byte[] Ctr; + + public NcaFsHeader(BinaryReader reader) + { + long start = reader.BaseStream.Position; + Version = reader.ReadInt16(); + FormatType = (NcaFormatType)reader.ReadByte(); + HashType = (NcaHashType)reader.ReadByte(); + EncryptionType = (NcaEncryptionType)reader.ReadByte(); + reader.BaseStream.Position += 3; + + switch (HashType) + { + case NcaHashType.Sha256: + Sha256Info = new Sha256Info(reader); + break; + case NcaHashType.Ivfc: + IvfcInfo = new IvfcHeader(reader); + break; + } + + if (EncryptionType == NcaEncryptionType.AesCtrEx) + { + BktrInfo = new BktrPatchInfo(); + + reader.BaseStream.Position = start + 0x100; + + BktrInfo.RelocationHeader = new BktrHeader(reader); + BktrInfo.EncryptionHeader = new BktrHeader(reader); + } + + if (FormatType == NcaFormatType.Pfs0) + { + Type = SectionType.Pfs0; + } + else if (FormatType == NcaFormatType.Romfs) + { + if (EncryptionType == NcaEncryptionType.AesCtrEx) + { + Type = SectionType.Bktr; + } + else + { + Type = SectionType.Romfs; + } + } + + reader.BaseStream.Position = start + 0x140; + Ctr = reader.ReadBytes(8).Reverse().ToArray(); + + reader.BaseStream.Position = start + 512; + } + } +} diff --git a/src/LibHac/NcaStructs.cs b/src/LibHac/IO/NcaUtils/NcaStructs.cs similarity index 74% rename from src/LibHac/NcaStructs.cs rename to src/LibHac/IO/NcaUtils/NcaStructs.cs index 07563588..c1c34095 100644 --- a/src/LibHac/NcaStructs.cs +++ b/src/LibHac/IO/NcaUtils/NcaStructs.cs @@ -1,8 +1,6 @@ using System.IO; -using System.Linq; -using LibHac.IO; -namespace LibHac +namespace LibHac.IO.NcaUtils { public class NcaHeader { @@ -98,72 +96,6 @@ namespace LibHac } } - public class NcaFsHeader - { - public short Version; - public NcaFormatType FormatType; - public NcaHashType HashType; - public NcaEncryptionType EncryptionType; - public SectionType Type; - - public IvfcHeader IvfcInfo; - public Sha256Info Sha256Info; - public BktrPatchInfo BktrInfo; - - public byte[] Ctr; - - public NcaFsHeader(BinaryReader reader) - { - long start = reader.BaseStream.Position; - Version = reader.ReadInt16(); - FormatType = (NcaFormatType)reader.ReadByte(); - HashType = (NcaHashType)reader.ReadByte(); - EncryptionType = (NcaEncryptionType)reader.ReadByte(); - reader.BaseStream.Position += 3; - - switch (HashType) - { - case NcaHashType.Sha256: - Sha256Info = new Sha256Info(reader); - break; - case NcaHashType.Ivfc: - IvfcInfo = new IvfcHeader(reader); - break; - } - - if (EncryptionType == NcaEncryptionType.AesCtrEx) - { - BktrInfo = new BktrPatchInfo(); - - reader.BaseStream.Position = start + 0x100; - - BktrInfo.RelocationHeader = new BktrHeader(reader); - BktrInfo.EncryptionHeader = new BktrHeader(reader); - } - - if (FormatType == NcaFormatType.Pfs0) - { - Type = SectionType.Pfs0; - } - else if (FormatType == NcaFormatType.Romfs) - { - if (EncryptionType == NcaEncryptionType.AesCtrEx) - { - Type = SectionType.Bktr; - } - else - { - Type = SectionType.Romfs; - } - } - - reader.BaseStream.Position = start + 0x140; - Ctr = reader.ReadBytes(8).Reverse().ToArray(); - - reader.BaseStream.Position = start + 512; - } - } - public class BktrPatchInfo { public BktrHeader RelocationHeader; @@ -174,7 +106,7 @@ namespace LibHac { public byte[] MasterHash; public int BlockSize; // In bytes - public uint Always2; + public uint LevelCount; public long HashTableOffset; public long HashTableSize; public long DataOffset; @@ -187,7 +119,7 @@ namespace LibHac { MasterHash = reader.ReadBytes(0x20); BlockSize = reader.ReadInt32(); - Always2 = reader.ReadUInt32(); + LevelCount = reader.ReadUInt32(); HashTableOffset = reader.ReadInt64(); HashTableSize = reader.ReadInt64(); DataOffset = reader.ReadInt64(); @@ -261,6 +193,13 @@ namespace LibHac Logo }; + public enum NcaSectionType + { + Code, + Data, + Logo + }; + public enum ContentType { Program, @@ -307,12 +246,4 @@ namespace LibHac Romfs, Bktr } - - public enum Validity : byte - { - Unchecked, - Invalid, - Valid, - MissingKey - } } diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 89151044..b949fe3f 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using LibHac.IO; -using LibHac.IO.RomFs; +using LibHac.IO.NcaUtils; using LibHac.IO.Save; namespace LibHac @@ -127,10 +127,10 @@ namespace LibHac { var title = new Title(); - // Meta contents always have 1 Partition FS section with 1 file in it - IStorage sect = nca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true); - var pfs0 = new PartitionFileSystem(sect); - IFile file = pfs0.OpenFile(pfs0.Files[0], OpenMode.Read); + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + string cnmtPath = fs.EnumerateEntries("*.cnmt").Single().FullPath; + + IFile file = fs.OpenFile(cnmtPath, OpenMode.Read); var metadata = new Cnmt(file.AsStream()); title.Id = metadata.TitleId; @@ -168,7 +168,7 @@ namespace LibHac { foreach (Title title in Titles.Values.Where(x => x.ControlNca != null)) { - var romfs = new RomFsFileSystem(title.ControlNca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true)); + IFileSystem romfs = title.ControlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); IFile control = romfs.OpenFile("control.nacp", OpenMode.Read); title.Control = new Nacp(control.AsStream()); diff --git a/src/LibHac/Validity.cs b/src/LibHac/Validity.cs new file mode 100644 index 00000000..424dae7c --- /dev/null +++ b/src/LibHac/Validity.cs @@ -0,0 +1,10 @@ +namespace LibHac +{ + public enum Validity : byte + { + Unchecked, + Invalid, + Valid, + MissingKey + } +} diff --git a/src/hactoolnet/ProcessDelta.cs b/src/hactoolnet/ProcessDelta.cs index 85934724..e0be4aa6 100644 --- a/src/hactoolnet/ProcessDelta.cs +++ b/src/hactoolnet/ProcessDelta.cs @@ -2,8 +2,8 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; -using LibHac; using LibHac.IO; +using LibHac.IO.NcaUtils; using static hactoolnet.Print; namespace hactoolnet @@ -26,7 +26,7 @@ namespace hactoolnet try { var nca = new Nca(ctx.Keyset, deltaStorage, true); - IFileSystem fs = nca.OpenSectionFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid); + IFileSystem fs = nca.OpenFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid); if (!fs.FileExists(FragmentFileName)) { diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index 18990437..3f5e1d30 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using LibHac; using LibHac.IO; -using LibHac.IO.RomFs; +using LibHac.IO.NcaUtils; using static hactoolnet.Print; namespace hactoolnet @@ -45,7 +45,7 @@ namespace hactoolnet if (ctx.Options.ListRomFs && nca.Sections[1] != null) { - var romfs = new RomFsFileSystem(nca.OpenSection(1, false, ctx.Options.IntegrityLevel, true)); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, ctx.Options.IntegrityLevel); foreach (DirectoryEntry entry in romfs.EnumerateEntries()) { @@ -76,14 +76,14 @@ namespace hactoolnet if (ctx.Options.RomfsOutDir != null) { - IFileSystem romfs = nca.OpenSectionFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); + IFileSystem romfs = nca.OpenFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); } if (ctx.Options.ReadBench) { long bytesToRead = 1024L * 1024 * 1024 * 5; - IStorage storage = nca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel, true); + IStorage storage = nca.OpenStorage(section.SectionNum, ctx.Options.IntegrityLevel); var dest = new NullStorage(storage.Length); int iterations = (int)(bytesToRead / storage.Length) + 1; @@ -125,7 +125,7 @@ namespace hactoolnet if (ctx.Options.ExefsOutDir != null) { - IFileSystem pfs = nca.OpenSectionFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); + IFileSystem pfs = nca.OpenFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); pfs.Extract(ctx.Options.ExefsOutDir, ctx.Logger); } } diff --git a/src/hactoolnet/ProcessPfs.cs b/src/hactoolnet/ProcessPfs.cs index 4dedebdd..a2ffb86b 100644 --- a/src/hactoolnet/ProcessPfs.cs +++ b/src/hactoolnet/ProcessPfs.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Text; using LibHac; using LibHac.IO; +using LibHac.IO.NcaUtils; using static hactoolnet.Print; namespace hactoolnet diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index b82ad93a..85528af4 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using LibHac; using LibHac.IO; -using LibHac.IO.RomFs; +using LibHac.IO.NcaUtils; using LibHac.IO.Save; namespace hactoolnet @@ -119,7 +119,7 @@ namespace hactoolnet if (ctx.Options.RomfsOutDir != null) { - var romfs = new RomFsFileSystem(title.MainNca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel, true)); + IFileSystem romfs = title.MainNca.OpenFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); } diff --git a/src/hactoolnet/ProcessXci.cs b/src/hactoolnet/ProcessXci.cs index 843381ae..cca5bbce 100644 --- a/src/hactoolnet/ProcessXci.cs +++ b/src/hactoolnet/ProcessXci.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using LibHac; using LibHac.IO; -using LibHac.IO.RomFs; +using LibHac.IO.NcaUtils; using static hactoolnet.Print; namespace hactoolnet @@ -111,7 +111,7 @@ namespace hactoolnet if (ctx.Options.RomfsOutDir != null) { - var romfs = new RomFsFileSystem(mainNca.OpenSection(romfsSection.SectionNum, false, ctx.Options.IntegrityLevel, true)); + IFileSystem romfs = mainNca.OpenFileSystem(romfsSection.SectionNum, ctx.Options.IntegrityLevel); romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); }