public static long Build(Stream output, IList <IArchiveFileInfo> files) { var hash = new Kryptography.Hash.Sha256(); using var bw = new BinaryWriterX(output, true); var inOffset = output.Position; // Write file data bw.BaseStream.Position = inOffset + _exeFsHeaderSize; var filePosition = bw.BaseStream.Position; IList <NcchExeFsFileEntry> fileEntries = new List <NcchExeFsFileEntry>(MaxFiles_); IList <NcchExeFsFileEntryHash> fileHashes = new List <NcchExeFsFileEntryHash>(MaxFiles_); var fileOffset = 0; foreach (var file in files.Cast <ArchiveFileInfo>()) { var writtenSize = file.SaveFileData(bw.BaseStream); bw.WriteAlignment(MediaSize_); fileEntries.Add(new NcchExeFsFileEntry { name = file.FilePath.GetName().PadRight(8, '\0'), offset = fileOffset, size = (int)writtenSize }); fileHashes.Add(new NcchExeFsFileEntryHash { hash = hash.Compute(new SubStream(output, filePosition + fileOffset, writtenSize)) }); fileOffset = (int)(bw.BaseStream.Position - filePosition); } var finalSize = bw.BaseStream.Position - inOffset; // Write file entries bw.BaseStream.Position = inOffset; bw.WriteMultiple(fileEntries); bw.WritePadding(_exeFsFileEntrySize * (MaxFiles_ - fileEntries.Count)); // Write reserved data bw.WritePadding(0x20); // Write file entry hashes bw.WritePadding(_exeFsFileEntryHashSize * (MaxFiles_ - fileEntries.Count)); bw.WriteMultiple(fileHashes.Reverse()); output.Position = inOffset + finalSize; return(finalSize); }
/// <summary> /// Write IVFC hash levels. /// </summary> /// <param name="output">The stream to write to.</param> /// <param name="metaDataPosition">The position of the initial data to hash.</param> /// <param name="metaDataSize">The position at which to start writing.</param> /// <param name="masterHashPosition">The separate position at which the master hash level is written.</param> /// <param name="levels">Number of levels to write.</param> /// <returns>Position and size of each written level.</returns> private static IList <(long, long, long)> WriteIvfcLevels(Stream output, long metaDataPosition, long metaDataSize, long masterHashPosition, int levels) { // Pre-calculate hash level sizes var hashLevelSizes = new long[levels]; var alignedMetaDataSize = (metaDataSize + BlockSize_ - 1) & ~(BlockSize_ - 1); for (var level = 0; level < levels - 1; level++) { var previousSize = level == 0 ? alignedMetaDataSize : hashLevelSizes[level - 1]; var levelSize = previousSize / BlockSize_ * 0x20; var alignedLevelSize = (levelSize + BlockSize_ - 1) & ~(BlockSize_ - 1); hashLevelSizes[level] = alignedLevelSize; } // Pre-calculate hash level position var hashLevelPositions = new long[levels]; var alignedMetaDataPosition = (metaDataPosition + BlockSize_ - 1) & ~(BlockSize_ - 1); for (var level = 0; level < levels - 1; level++) { var levelPosition = alignedMetaDataPosition + alignedMetaDataSize + hashLevelSizes.Skip(level + 1).Take(levels - level - 2).Sum(x => x); hashLevelPositions[level] = levelPosition; } // Add master hash position hashLevelSizes[levels - 1] = BlockSize_; hashLevelPositions[levels - 1] = masterHashPosition; // Write hash levels var result = new List <(long, long, long)>(); var sha256 = new Kryptography.Hash.Sha256(); var previousLevelPosition = alignedMetaDataPosition; var previousLevelSize = alignedMetaDataSize; for (var level = 0; level < levels; level++) { var previousLevelStream = new SubStream(output, previousLevelPosition, previousLevelSize); var levelStream = new SubStream(output, hashLevelPositions[level], hashLevelSizes[level]); var block = new byte[BlockSize_]; while (previousLevelStream.Position < previousLevelStream.Length) { previousLevelStream.Read(block, 0, BlockSize_); var hash = sha256.Compute(block); levelStream.Write(hash); } result.Add((hashLevelPositions[level], levelStream.Position, hashLevelSizes[level])); previousLevelPosition = hashLevelPositions[level]; previousLevelSize = hashLevelSizes[level]; } //var dataPosition = metaDataPosition; //var writePosition = hashLevelInformation[0]; //var dataSize = writePosition - dataPosition; //for (var level = 0; level < levels; level++) //{ // bw.BaseStream.Position = writePosition; // var dataEnd = dataPosition + dataSize; // while (dataPosition < dataEnd) // { // var blockSize = Math.Min(BlockSize_, dataEnd - dataPosition); // var hash = sha256.Compute(new SubStream(output, dataPosition, blockSize)); // bw.Write(hash); // dataPosition += BlockSize_; // } // dataPosition = writePosition; // dataSize = bw.BaseStream.Position - writePosition; // writePosition = level + 1 >= levels - 1 ? masterHashPosition : hashLevelInformation[level + 1]; // // Pad hash level to next block // // Do not pad master hash level // // TODO: Make general padding code that also works with unaligned master hash position // var alignSize = 0L; // if (level + 1 < levels - 1) // { // alignSize = ((dataSize + BlockSize_ - 1) & ~(BlockSize_ - 1)) - dataSize; // bw.WritePadding((int)alignSize); // } // result.Add((dataPosition, dataSize, dataSize + alignSize)); //} return(result); }
public void Save(Stream output, IList <ArchiveFileInfo> files) { var sha256 = new Kryptography.Hash.Sha256(); using var bw = new BinaryWriterX(output); bw.BaseStream.Position = _ncchHeaderSize; // Write and update exHeader information var exHeaderFile = files.FirstOrDefault(f => f.FilePath.GetName() == ExHeaderFileName_); if (exHeaderFile != null) { var exHeaderPosition = bw.BaseStream.Position; var writtenSize = exHeaderFile.SaveFileData(output); bw.WriteAlignment(MediaSize_); _ncchHeader.exHeaderSize = (int)(exHeaderFile.FileSize / 2); _ncchHeader.exHeaderHash = sha256.Compute(new SubStream(output, exHeaderPosition, _ncchHeader.exHeaderSize)); } else { Array.Clear(_ncchHeader.exHeaderHash, 0, 0x20); _ncchHeader.exHeaderSize = 0; } // Write and update logo region information var logoRegionFile = files.FirstOrDefault(f => f.FilePath.GetName() == LogoRegionFileName_); if (logoRegionFile != null) { var logoRegionPosition = bw.BaseStream.Position; var writtenSize = logoRegionFile.SaveFileData(output); bw.WriteAlignment(MediaSize_); _ncchHeader.logoRegionOffset = (int)(logoRegionPosition / MediaSize_); _ncchHeader.logoRegionSize = (int)((bw.BaseStream.Position - logoRegionPosition) / MediaSize_); _ncchHeader.logoRegionHash = sha256.Compute(new SubStream(output, logoRegionPosition, writtenSize)); } else { _ncchHeader.logoRegionOffset = 0; _ncchHeader.logoRegionSize = 0; Array.Clear(_ncchHeader.logoRegionHash, 0, 0x20); } // Write and update plain region information var plainRegionFile = files.FirstOrDefault(f => f.FilePath.GetName() == PlainRegionFileName_); if (plainRegionFile != null) { var plainRegionPosition = bw.BaseStream.Position; plainRegionFile.SaveFileData(output); bw.WriteAlignment(MediaSize_); _ncchHeader.plainRegionOffset = (int)(plainRegionPosition / MediaSize_); _ncchHeader.plainRegionSize = (int)((bw.BaseStream.Position - plainRegionPosition) / MediaSize_); } else { _ncchHeader.plainRegionOffset = 0; _ncchHeader.plainRegionSize = 0; } // Write and update ExeFs var exeFsFiles = files.Where(x => x.FilePath.ToRelative().IsInDirectory(ExeFsFolder_, true)).ToArray(); if (exeFsFiles.Any()) { var exeFsPosition = bw.BaseStream.Position; var exeFsSize = ExeFsBuilder.Build(output, exeFsFiles); _ncchHeader.exeFsOffset = (int)(exeFsPosition / MediaSize_); _ncchHeader.exeFsSize = (int)(exeFsSize / MediaSize_); _ncchHeader.exeFsHashRegionSize = _exeFsHeaderSize / MediaSize_; _ncchHeader.exeFsSuperBlockHash = sha256.Compute(new SubStream(output, exeFsPosition, _exeFsHeaderSize)); bw.WriteAlignment(0x1000); } else { _ncchHeader.exeFsOffset = 0; _ncchHeader.exeFsSize = 0; _ncchHeader.exeFsHashRegionSize = 0; Array.Clear(_ncchHeader.exeFsSuperBlockHash, 0, 0x20); } // Write and update RomFs var romFsFiles = files.Where(x => x.FilePath.ToRelative().IsInDirectory(RomFsFolder_, true)).ToArray(); if (romFsFiles.Any()) { var romFsPosition = bw.BaseStream.Position; var romFsSize1 = RomFsBuilder.CalculateRomFsSize(romFsFiles, RomFsFolder_); var buffer = new byte[0x4000]; var size = romFsSize1; while (size > 0) { var length = (int)Math.Min(size, 0x4000); bw.BaseStream.Write(buffer, 0, length); size -= length; } var romFsStream = new SubStream(bw.BaseStream, romFsPosition, romFsSize1); var(_, _) = RomFsBuilder.Build(romFsStream, romFsFiles, RomFsFolder_); _ncchHeader.romFsOffset = (int)(romFsPosition / MediaSize_); _ncchHeader.romFsSize = (int)(romFsSize1 / MediaSize_); _ncchHeader.romFsHashRegionSize = 1; // Only the first 0x200 of the RomFs get into the hash region apparently _ncchHeader.romFsSuperBlockHash = sha256.Compute(new SubStream(output, romFsPosition, MediaSize_)); } else { _ncchHeader.romFsOffset = 0; _ncchHeader.romFsSize = 0; _ncchHeader.romFsHashRegionSize = 0; Array.Clear(_ncchHeader.romFsSuperBlockHash, 0, 0x20); } // Write header // HINT: Set NCCH flags to NoCrypto mode _ncchHeader.ncchFlags[7] = 4; _ncchHeader.ncchSize = (int)(output.Length / MediaSize_); bw.BaseStream.Position = 0; bw.WriteType(_ncchHeader); }