public NCCH(Stream input) { _stream = input; using (var br = new BinaryReaderX(input, true)) { //NCCH Header ncchHeader = br.ReadStruct <Header>(); if (ncchHeader.ncchSize != input.Length / mediaUnitSize) { throw new Exception("Size in header is not the same as the file."); } //ExtHeader if (ncchHeader.exHeaderSize != 0) { var exHeaderOffset = br.BaseStream.Position; exHeader = br.ReadStruct <ExHeader>(); Files.Add(new ArchiveFileInfo { State = ArchiveFileState.Archived, FileName = "ExHeader.bin", FileData = new SubStream(br.BaseStream, exHeaderOffset, ncchHeader.exHeaderSize * 2) }); } //Plain Region if (ncchHeader.plainRegionOffset != 0 && ncchHeader.plainRegionSize != 0) { br.BaseStream.Position = ncchHeader.plainRegionOffset * mediaUnitSize; plainRegion = br.ReadBytes(ncchHeader.plainRegionSize * mediaUnitSize); Files.Add(new ArchiveFileInfo { State = ArchiveFileState.Archived, FileName = "PlainRegion.bin", FileData = new SubStream(br.BaseStream, ncchHeader.plainRegionOffset * mediaUnitSize, ncchHeader.plainRegionSize * mediaUnitSize) }); } //Logo Region if (ncchHeader.logoRegionOffset != 0 && ncchHeader.logoRegionSize != 0) { br.BaseStream.Position = ncchHeader.logoRegionOffset * mediaUnitSize; logoRegion = br.ReadBytes(ncchHeader.logoRegionSize * mediaUnitSize); Files.Add(new ArchiveFileInfo { State = ArchiveFileState.Archived, FileName = "Logo.icn", FileData = new SubStream(br.BaseStream, ncchHeader.logoRegionOffset * mediaUnitSize, ncchHeader.logoRegionSize * mediaUnitSize) }); } //ExeFS int exeFSHeaderSize = 0x200; if (ncchHeader.exeFSOffset != 0 && ncchHeader.exeFSSize != 0) { br.BaseStream.Position = ncchHeader.exeFSOffset * mediaUnitSize; exeFS = new ExeFS(br.BaseStream); foreach (var exeFsFile in exeFS.fileHeader) { if (exeFsFile.offset == 0 && exeFsFile.size == 0) { break; } Files.Add(new ExeFSFileInfo { State = ArchiveFileState.Archived, FileName = "ExeFS\\" + exeFsFile.name, FileData = new SubStream(br.BaseStream, ncchHeader.exeFSOffset * mediaUnitSize + exeFSHeaderSize + exeFsFile.offset, exeFsFile.size), compressed = exeFsFile.name == ".code" && (exHeader.sci.flag & 0x1) == 1 }); } } //RomFS if (ncchHeader.romFSOffset != 0 && ncchHeader.romFSSize != 0) { br.BaseStream.Position = ncchHeader.romFSOffset * mediaUnitSize; romFS = new RomFS(br.BaseStream); foreach (var romFSFile in romFS.files) { Files.Add(new ArchiveFileInfo { State = ArchiveFileState.Archived, FileName = "RomFS" + romFSFile.fileName, FileData = new SubStream(br.BaseStream, romFSFile.fileOffset, romFSFile.fileSize) }); } } } }
/// <summary> /// Writes the current state of the NCCH partition to the given binary data accessor /// </summary> /// <param name="data">Data accessor to receive the binary data</param> /// <returns>A long representing the total length of data written</returns> public async Task <long> WriteBinary(IWriteOnlyBinaryDataAccessor data) { // Get the data var exheader = ExHeader?.ToByteArray(); var plainRegion = !string.IsNullOrEmpty(PlainRegion) ? Encoding.ASCII.GetBytes(PlainRegion) : null; var plainRegionOffset = 0; var logoRegionOffset = 0; var exeFs = ExeFs?.ToByteArray(); var exeFsOffset = 0; var romFs = RomFs?.Data; var romFsOffset = 0; // Write the data var offset = 0x200; // Skip the header, write it last if (exheader != null) { await data.WriteAsync(offset, exheader); offset += exheader.Length; } if (plainRegion != null) { plainRegionOffset = offset; await data.WriteAsync(offset, plainRegion); offset += plainRegion.Length; var padding = new byte[0x200 - plainRegion.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (Logo != null) { logoRegionOffset = offset; await data.WriteAsync(offset, Logo); offset += Logo.Length; var padding = new byte[0x200 - Logo.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (exeFs != null) { exeFsOffset = offset; await data.WriteAsync(offset, exeFs); offset += exeFs.Length; var padding = new byte[0x200 - exeFs.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (romFs != null) { romFsOffset = offset; const int bufferSize = 1024 * 1024; for (int i = 0; i < romFs.Length; i += bufferSize) { int length = (int)Math.Min(bufferSize, romFs.Length - i); var block = await romFs.ReadArrayAsync(i, length); await data.WriteAsync(offset, block); offset += length; } var padding = new byte[0x200 - romFs.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } // Create a new header using var sha = SHA256.Create(); var header = NcchHeader.Copy(this.Header); header.Signature = new byte[0x100]; // We lack the 3DS's private key, so leave out the signature header.ContentSize = (offset + MediaUnitSize - 1) / MediaUnitSize; // offset/MediaUnitSize, but rounding up header.ContentLockSeedHash = 0; // Unknown, left blank by SciresM's 3DS Builder if (Logo != null) { header.LogoRegionHash = sha.ComputeHash(Logo); } else { header.LogoRegionHash = new byte[0x20]; } if (exheader != null) { header.ExHeaderHash = NcchExtendedHeader.GetSuperblockHash(sha, exheader); header.ExHeaderSize = NcchExtendedHeader.ExHeaderDataSize; } else { header.ExHeaderHash = new byte[0x20]; header.ExHeaderSize = 0; } header.PlainRegionOffset = (plainRegionOffset + MediaUnitSize - 1) / MediaUnitSize; header.PlainRegionSize = ((plainRegion?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.LogoRegionOffset = (logoRegionOffset + MediaUnitSize - 1) / MediaUnitSize; header.LogoRegionSize = ((Logo?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsOffset = (exeFsOffset + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsSize = ((exeFs?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsHashRegionSize = 1; // Static 0x200 for exefs superblock header.RomFsOffset = (romFsOffset + MediaUnitSize - 1) / MediaUnitSize; header.RomFsSize = ((int)(romFs?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.RomFsHashRegionSize = ((RomFs?.Header?.MasterHashSize ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsSuperblockHash = ExeFs?.GetSuperblockHash() ?? new byte[0x20]; header.RomFsSuperblockHash = RomFs != null ? await RomFs.GetSuperblockHash(sha, romFs, RomFs.Header) : new byte[0x20]; var headerData = await header.ToBinary().ReadArrayAsync(); await data.WriteAsync(0, headerData); return(offset); }