Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        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);
        }