示例#1
0
        public void Save(Stream bnkStream, Stream mbnkStream)
        {
            Header.NumFiles   = (uint)Files.Count;
            Header.HeaderSize = (uint)(Marshal.SizeOf(typeof(SoundbankHeader)) + (Marshal.SizeOf(typeof(SoundbankEntryInfo)) * Header.NumFiles));
            bnkStream.WriteStruct(Header);
            mbnkStream.WriteStruct(Header);
            uint nextOffset = Header.HeaderSize;

            nextOffset = nextOffset.Align(0x800);
            foreach (var entry in Files)
            {
                entry.Info.Offset = nextOffset;
                nextOffset       += entry.Info.MetadataLength + entry.Info.AudioLength;
                nextOffset        = nextOffset.Align(0x800);
                bnkStream.WriteStruct(entry.Info);
                mbnkStream.WriteStruct(entry.Info);
            }

            int count = 0;

            foreach (var entry in Files)
            {
                bnkStream.Seek(entry.Info.Offset, SeekOrigin.Begin);
                Stream metadataStream = m_MetadataStreams[count];
                if (metadataStream != null)
                {
                    metadataStream.Seek(0, SeekOrigin.Begin);
                    metadataStream.CopyTo(bnkStream);
                }
                Stream audioStream = m_AudioStreams[count];
                audioStream.CopyTo(bnkStream);
                count++;
            }
        }
示例#2
0
        /// <inheritdoc />
        public void UpdateOffsets(uint newFileOffset, uint newRva)
        {
            FileOffset    = newFileOffset;
            Rva           = newRva;
            _physicalSize = 0;
            _virtualSize  = 0;

            foreach (var item in _items)
            {
                uint physicalPadding = newFileOffset.Align(item.Alignment) - newFileOffset;
                uint virtualPadding  = newRva.Align(item.Alignment) - newRva;

                newFileOffset += physicalPadding;
                newRva        += virtualPadding;

                item.Segment.UpdateOffsets(newFileOffset, newRva);

                uint physicalSize = item.Segment.GetPhysicalSize();
                uint virtualSize  = item.Segment.GetVirtualSize();

                newFileOffset += physicalSize;
                newRva        += virtualSize;
                _physicalSize += physicalPadding + physicalSize;
                _virtualSize  += virtualPadding + virtualSize;
            }
        }
        public bool Validate(string path, out ArchiveValidateError error)
        {
            byte[] buffer = new byte[256 - 4];

            using (var input = File.Open(
                       path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                while (input.Position < input.Length)
                {
                    if (input.Position + 256 > input.Length)
                    {
                        error = new ArchiveValidateError(
                            "end of file for next entry",
                            input);
                        return(false);
                    }

                    input.Read(buffer, 0, buffer.Length);

                    int length = CheckFileName(buffer);
                    if (length < 0)
                    {
                        error = new ArchiveValidateError(
                            "invalid file name",
                            input);
                        return(false);
                    }

                    uint size     = input.ReadValueU32();
                    uint realSize = size.Align(64);

                    if (size == 0 && length == 0)
                    {
                        // this is the last entry
                        if (input.Position == input.Length)
                        {
                            break;
                        }

                        error = new ArchiveValidateError(
                            "null entry not at end of file",
                            input);
                        return(false);
                    }

                    if (input.Position + realSize > input.Length)
                    {
                        error = new ArchiveValidateError(
                            "end of file for data",
                            input);
                        return(false);
                    }

                    input.Seek(realSize, SeekOrigin.Current);
                }

                error = null;
                return(true);
            }
        }
示例#4
0
        /// <inheritdoc />
        public override uint GetPhysicalSize()
        {
            uint size = VersionTableEntryHeader.GetHeaderSize(Key);

            size  = size.Align(4);
            size += (uint)Values.Count * sizeof(uint);
            return(size);
        }
        public List <ArchiveEntry> GetEntries(string path)
        {
            byte[] buffer  = new byte[256 - 4];
            var    entries = new List <ArchiveEntry>();

            using (var input = File.Open(
                       path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                while (input.Position < input.Length)
                {
                    if (input.Position + 256 > input.Length)
                    {
                        return(null);
                    }

                    input.Read(buffer, 0, buffer.Length);

                    int length = CheckFileName(buffer);
                    if (length < 0)
                    {
                        return(null);
                    }

                    uint size     = input.ReadValueU32();
                    uint realSize = size.Align(64);

                    if (size == 0 && length == 0)
                    {
                        // this is the last entry
                        if (input.Position == input.Length)
                        {
                            break;
                        }

                        return(null);
                    }

                    if (input.Position + realSize > input.Length)
                    {
                        return(null);
                    }

                    entries.Add(new ArchiveEntry()
                    {
                        Name   = GetUniqueName(Encoding.ASCII.GetString(buffer, 0, length), entries),
                        Offset = input.Position,
                        Size   = size,
                    });

                    input.Seek(realSize, SeekOrigin.Current);
                }

                return(entries);
            }
        }
示例#6
0
        /// <inheritdoc />
        public override uint GetPhysicalSize()
        {
            uint size = VersionTableEntryHeader.GetHeaderSize(Key);

            for (int i = 0; i < Tables.Count; i++)
            {
                size  = size.Align(4);
                size += Tables[i].GetPhysicalSize();
            }

            return(size);
        }
示例#7
0
        private MemoryStream ReadAlignedBlock(Stream input)
        {
            uint tableLength   = input.ReadValueU32();
            uint alignedLength = tableLength.Align(16);

            var memory = input.ReadToMemoryStream(tableLength);

            if (alignedLength != tableLength)
            {
                input.Seek(alignedLength - tableLength, SeekOrigin.Current);
            }

            return(memory);
        }
示例#8
0
        /// <summary>
        /// Aligns all sections according to the file and section alignment properties in the optional header.
        /// </summary>
        public void AlignSections()
        {
            for (int i = 0; i < Sections.Count; i++)
            {
                var section = Sections[i];

                uint fileOffset = i > 0
                    ? Sections[i - 1].FileOffset + Sections[i - 1].GetPhysicalSize()
                    : OptionalHeader.SizeOfHeaders;
                uint rva = i > 0
                    ? Sections[i - 1].Rva + Sections[i - 1].GetVirtualSize()
                    : OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment);

                section.UpdateOffsets(
                    fileOffset.Align(OptionalHeader.FileAlignment),
                    rva.Align(OptionalHeader.SectionAlignment));
            }
        }
示例#9
0
        public void CreateFromDirectory(string Dir, string Outfile)
        {
            string[] Filepaths = System.IO.Directory.GetFiles(Dir);

            ushort Filecount = (ushort)Filepaths.Length;
            uint   RequiredBytesForHeader = Util.Align(Filecount * 4u + 4u, 0x50u);             // pretty sure this is not right but should work
            var    Filestream             = new System.IO.FileStream(Outfile, System.IO.FileMode.Create);

            // header
            Filestream.WriteByte((byte)'S'); Filestream.WriteByte((byte)'C');
            Filestream.WriteByte((byte)'M'); Filestream.WriteByte((byte)'P');
            uint TotalFilesize = RequiredBytesForHeader;

            foreach (string Path in Filepaths)
            {
                Filestream.Write(BitConverter.GetBytes(TotalFilesize), 0, 4);
                TotalFilesize += (uint)(new System.IO.FileInfo(Path).Length);
                TotalFilesize  = TotalFilesize.Align(0x10u);
            }
            while (Filestream.Length < RequiredBytesForHeader)
            {
                Filestream.WriteByte(0x00);
            }

            // files
            foreach (string Path in Filepaths)
            {
                var File = new System.IO.FileStream(Path, System.IO.FileMode.Open);
                Util.CopyStream(File, Filestream, (int)File.Length);
                File.Close();
                while (Filestream.Length % 0x10 != 0)
                {
                    Filestream.WriteByte(0x00);
                }
            }

            Filestream.Close();
        }
示例#10
0
        public void CreateFromDirectory(string Dir, string Outfile)
        {
            string[] Filepaths = System.IO.Directory.GetFiles(Dir);

            ushort Filecount = (ushort)Filepaths.Length;
            uint   RequiredBytesForHeader = NumberUtils.Align(Filecount * 3u + 5u, 0x10u);             // 3 bytes per filesize + 3 bytes for an extra pointer to first file + 2 bytes for filecount
            var    Filestream             = new System.IO.FileStream(Outfile, System.IO.FileMode.Create);

            // header
            Filestream.Write(BitConverter.GetBytes(Filecount), 0, 2);
            Filestream.Write(NumberUtils.GetBytesForUInt24(RequiredBytesForHeader), 0, 3);
            uint TotalFilesize = RequiredBytesForHeader;

            foreach (string Path in Filepaths)
            {
                TotalFilesize += (uint)(new System.IO.FileInfo(Path).Length);
                Filestream.Write(NumberUtils.GetBytesForUInt24(TotalFilesize), 0, 3);
                TotalFilesize = TotalFilesize.Align(0x10u);
            }
            while (Filestream.Length < RequiredBytesForHeader)
            {
                Filestream.WriteByte(0x00);
            }

            // files
            foreach (string Path in Filepaths)
            {
                var File = new System.IO.FileStream(Path, System.IO.FileMode.Open);
                StreamUtils.CopyStream(File, Filestream, (int)File.Length);
                File.Close();
                while (Filestream.Length % 0x10 != 0)
                {
                    Filestream.WriteByte(0x00);
                }
            }

            Filestream.Close();
        }
        /// <inheritdoc />
        public override void UpdateOffsets(uint newFileOffset, uint newRva)
        {
            base.UpdateOffsets(newFileOffset, newRva);

            uint offset = newFileOffset;

            foreach (var module in _modules)
            {
                foreach (var entry in module.Symbols)
                {
                    if (entry.IsImportByName)
                    {
                        _hintNameOffsets[entry] = offset - newFileOffset;
                        offset += (uint)(sizeof(ushort) + Encoding.ASCII.GetByteCount(entry.Name) + 1);
                        offset  = offset.Align(2);
                    }
                }
                _moduleNameOffsets[module] = offset - newFileOffset;
                offset += (uint)Encoding.ASCII.GetByteCount(module.Name) + 1;
            }

            _length = offset - newFileOffset;
        }
示例#12
0
        /// <summary>
        /// Aligns the writer to a specified boundary.
        /// </summary>
        /// <param name="writer">The writer to align.</param>
        /// <param name="align">The boundary to use.</param>
        public static void Align(this IBinaryStreamWriter writer, uint align)
        {
            uint currentPosition = writer.FileOffset;

            writer.WriteZeroes((int)(currentPosition.Align(align) - writer.FileOffset));
        }
示例#13
0
        protected void Build(
            TPackage package,
            IEnumerable <KeyValuePair <string, string> > paths,
            string outputPath,
            bool ps3)
        {
            var isCompressed = (package.Flags & Package.HeaderFlags.Compressed) != 0;
            var isCondensed  = (package.Flags & Package.HeaderFlags.Condensed) != 0;

            package.Entries.Clear();
            foreach (var kv in paths)
            {
                package.Entries.Add(new TEntry()
                {
                    Name = kv.Key,
                });
            }

            var baseOffset = package.EstimateHeaderSize();

            package.Entries.Clear();
            using (var output = File.Create(outputPath))
            {
                if (isCondensed == true &&
                    isCompressed == true)
                {
                    output.Seek(baseOffset, SeekOrigin.Begin);

                    using (var compressed = new MemoryStream())
                    {
                        var z = new ZLIB.ZOutputStream(compressed, ZLIB.zlibConst.Z_BEST_COMPRESSION);
                        z.FlushMode = ZLIB.zlibConst.Z_SYNC_FLUSH;

                        long offset = 0;
                        foreach (var kv in paths)
                        {
                            using (var input = File.OpenRead(kv.Value))
                            {
                                var entry = new TEntry();
                                entry.Name             = kv.Key;
                                entry.Offset           = (uint)offset;
                                entry.UncompressedSize = (uint)input.Length;

                                long size = z.TotalOut;

                                z.WriteFromStream(input, input.Length);

                                size = z.TotalOut - size;

                                entry.CompressedSize = (uint)size;

                                offset += entry.UncompressedSize;

                                package.Entries.Add(entry);
                            }
                        }

                        package.CompressedSize   = (uint)compressed.Length;
                        package.UncompressedSize = (uint)offset;

                        compressed.Position = 0;
                        output.WriteFromStream(compressed, compressed.Length);
                    }

                    output.Seek(0, SeekOrigin.Begin);
                    package.Serialize(output);
                }
                else if (
                    ps3 == true &&
                    isCompressed == true)
                {
                    output.Seek(baseOffset, SeekOrigin.Begin);

                    long offset           = 0;
                    uint uncompressedSize = 0;

                    foreach (var kv in paths)
                    {
                        using (var input = File.OpenRead(kv.Value))
                        {
                            if (isCondensed == false)
                            {
                                var offsetPadding =
                                    offset.Align(2048) - offset;
                                if (offsetPadding > 0)
                                {
                                    offset += offsetPadding;
                                    output.Seek(offsetPadding, SeekOrigin.Current);
                                }

                                var sizePadding =
                                    uncompressedSize.Align(2048) - uncompressedSize;
                                if (sizePadding > 0)
                                {
                                    uncompressedSize += sizePadding;
                                }
                            }

                            var entry = new TEntry();
                            entry.Name             = kv.Key;
                            entry.Offset           = (uint)offset;
                            entry.UncompressedSize = (uint)input.Length;
                            entry.CompressedSize   = 0;

                            var left = input.Length;
                            while (left > 0)
                            {
                                using (var compressed = new MemoryStream())
                                {
                                    var chunkUncompressedSize = (uint)Math.Min(0x10000, left);

                                    var zlib = new DeflaterOutputStream(compressed, new Deflater(9, true));
                                    zlib.WriteFromStream(input, chunkUncompressedSize);
                                    zlib.Finish();

                                    var chunkCompressedSize = (uint)compressed.Length;

                                    if (chunkCompressedSize > 0xFFFF)
                                    {
                                        throw new InvalidOperationException();
                                    }

                                    output.WriteValueU16((ushort)chunkCompressedSize, package.Endian);
                                    output.WriteValueU16(0, package.Endian);
                                    output.WriteValueU32(chunkUncompressedSize, package.Endian);

                                    entry.CompressedSize += 2 + 2 + 4;
                                    entry.CompressedSize += chunkCompressedSize;

                                    compressed.Position = 0;
                                    output.WriteFromStream(compressed, compressed.Length);

                                    left -= chunkUncompressedSize;
                                }
                            }

                            offset           += entry.CompressedSize;
                            uncompressedSize += entry.UncompressedSize;

                            package.Entries.Add(entry);
                        }
                    }

                    package.CompressedSize   = (uint)offset;
                    package.UncompressedSize = uncompressedSize;
                }
                else if (isCompressed == true)
                {
                    output.Seek(baseOffset, SeekOrigin.Begin);

                    long offset           = 0;
                    uint uncompressedSize = 0;

                    foreach (var kv in paths)
                    {
                        using (var input = File.OpenRead(kv.Value))
                        {
                            if (isCondensed == false)
                            {
                                var offsetPadding =
                                    offset.Align(2048) - offset;
                                if (offsetPadding > 0)
                                {
                                    offset += offsetPadding;
                                    output.Seek(offsetPadding, SeekOrigin.Current);
                                }

                                var sizePadding =
                                    uncompressedSize.Align(2048) - uncompressedSize;
                                if (sizePadding > 0)
                                {
                                    uncompressedSize += sizePadding;
                                }
                            }

                            var entry = new TEntry();
                            entry.Name             = kv.Key;
                            entry.Offset           = (uint)offset;
                            entry.UncompressedSize = (uint)input.Length;

                            using (var compressed = new MemoryStream())
                            {
                                var zlib = new DeflaterOutputStream(compressed);
                                zlib.WriteFromStream(input, input.Length);
                                zlib.Finish();

                                entry.CompressedSize = (uint)compressed.Length;

                                compressed.Position = 0;
                                output.WriteFromStream(compressed, compressed.Length);
                            }

                            offset           += entry.CompressedSize;
                            uncompressedSize += entry.UncompressedSize;

                            package.Entries.Add(entry);
                        }
                    }

                    package.CompressedSize   = (uint)offset;
                    package.UncompressedSize = uncompressedSize;
                }
                else
                {
                    output.Seek(baseOffset, SeekOrigin.Begin);

                    long offset = 0;

                    foreach (var kv in paths)
                    {
                        using (var input = File.OpenRead(kv.Value))
                        {
                            if (isCondensed == false)
                            {
                                var padding = offset.Align(2048) - offset;
                                if (padding > 0)
                                {
                                    offset += padding;
                                    output.Seek(padding, SeekOrigin.Current);
                                }
                            }
                            else if (
                                isCondensed == true &&
                                isCompressed == false)
                            {
                                var padding = offset.Align(16) - offset;
                                if (padding > 0)
                                {
                                    offset += padding;
                                    output.Seek(padding, SeekOrigin.Current);
                                }
                            }

                            var entry = new TEntry();
                            entry.Name             = kv.Key;
                            entry.Offset           = (uint)offset;
                            entry.UncompressedSize = (uint)input.Length;

                            output.WriteFromStream(input, input.Length);

                            entry.CompressedSize = 0xFFFFFFFF;
                            offset += entry.UncompressedSize;

                            package.Entries.Add(entry);
                        }
                    }

                    package.CompressedSize   = 0xFFFFFFFF;
                    package.UncompressedSize = (uint)offset;
                }

                package.TotalSize = (uint)output.Length;
                output.Seek(0, SeekOrigin.Begin);
                package.Serialize(output);
            }
        }
示例#14
0
        public void Pack(string[] files, string outFilename, string metadata = null)
        {
            FileCount  = (uint)files.Length + 1;
            HeaderSize = 0x1C;

            EntrySize = 0;
            if (ContainsStartPointers)
            {
                EntrySize += 4;
            }
            if (ContainsEndPointers)
            {
                EntrySize += 4;
            }
            if (ContainsFileSizes)
            {
                EntrySize += 4;
            }
            if (ContainsFilenames)
            {
                EntrySize += 0x20;
            }
            if (ContainsFiletypes)
            {
                EntrySize += 4;
            }
            if (ContainsFileMetadata)
            {
                EntrySize += 4;
            }

            uint HeaderEnd = HeaderSize + EntrySize * FileCount;

            if (!String.IsNullOrEmpty(ArchiveName))
            {
                ArchiveNameLocation = HeaderEnd;
            }

            if (infile == null)                              // <- this is hacky and may create broken files when using -o with changed filenames/filecount/metadata/etc
            {
                FirstFileStart = HeaderEnd.Align(Alignment); // <- this is inaccurate and will break with metadata
            }

            using (FileStream f = new FileStream(outFilename, FileMode.Create)) {
                // header
                f.Write(Encoding.ASCII.GetBytes("FPS4"), 0, 4);
                f.WriteUInt32(FileCount.SwapEndian());
                f.WriteUInt32(HeaderSize.SwapEndian());
                f.WriteUInt32(FirstFileStart.SwapEndian());
                f.WriteUInt16(EntrySize.SwapEndian());
                f.WriteUInt16(ContentBitmask.SwapEndian());
                f.WriteUInt32(Unknown2.SwapEndian());
                f.WriteUInt32(ArchiveNameLocation.SwapEndian());

                // file list
                uint ptr = FirstFileStart;
                for (int i = 0; i < files.Length; ++i)
                {
                    var fi = new System.IO.FileInfo(files[i]);
                    if (ContainsStartPointers)
                    {
                        f.WriteUInt32(ptr.SwapEndian());
                    }
                    if (ContainsEndPointers)
                    {
                        f.WriteUInt32(((uint)(fi.Length.Align((int)Alignment))).SwapEndian());
                    }
                    if (ContainsFileSizes)
                    {
                        f.WriteUInt32(((uint)(fi.Length)).SwapEndian());
                    }
                    if (ContainsFilenames)
                    {
                        string filename = fi.Name.Truncate(0x1F);
                        byte[] fnbytes  = Util.ShiftJISEncoding.GetBytes(filename);
                        f.Write(fnbytes, 0, fnbytes.Length);
                        for (int j = fnbytes.Length; j < 0x20; ++j)
                        {
                            f.WriteByte(0);
                        }
                    }
                    if (ContainsFiletypes)
                    {
                        string extension = fi.Extension.TrimStart('.').Truncate(4);
                        byte[] extbytes  = Util.ShiftJISEncoding.GetBytes(extension);
                        f.Write(extbytes, 0, extbytes.Length);
                        for (int j = extbytes.Length; j < 4; ++j)
                        {
                            f.WriteByte(0);
                        }
                    }
                    if (ContainsFileMetadata)
                    {
                        if (infile != null)
                        {
                            // copy this from original file, very hacky when filenames/filecount/metadata changes
                            infile.Position = f.Position;
                            f.WriteUInt32(infile.ReadUInt32());
                        }
                        else
                        {
                            // place a null for now and go back to fix later
                            f.WriteUInt32(0);
                        }
                    }
                    ptr += (uint)fi.Length.Align((int)Alignment);
                }

                // at the end of the file list, a final entry pointing to the end of the container
                f.WriteUInt32(ptr.SwapEndian());
                for (int j = 4; j < EntrySize; ++j)
                {
                    f.WriteByte(0);
                }

                // the idea is to write a pointer here
                // and at the target of the pointer you have a nullterminated string
                // with all the metadata in a param=data format separated by spaces
                // maybe including a filepath at the start without a param=
                // strings should be after the filelist block but before the actual files
                if (ContainsFileMetadata && metadata != null)
                {
                    for (int i = 0; i < files.Length; ++i)
                    {
                        var  fi     = new System.IO.FileInfo(files[i]);
                        long ptrPos = 0x1C + ((i + 1) * EntrySize) - 4;

                        // remember pos
                        uint oldPos = (uint)f.Position;
                        // jump to metaptr
                        f.Position = ptrPos;
                        // write remembered pos
                        f.WriteUInt32(oldPos.SwapEndian());
                        // jump to remembered pos
                        f.Position = oldPos;
                        // write meta + nullterm
                        if (metadata.Contains('p'))
                        {
                            string relativePath = GetRelativePath(f.Name, fi.FullName);
                            f.Write(Util.ShiftJISEncoding.GetBytes(relativePath));
                        }
                        if (metadata.Contains('n'))
                        {
                            f.Write(Util.ShiftJISEncoding.GetBytes("name=" + Path.GetFileNameWithoutExtension(fi.FullName)));
                        }
                        f.WriteByte(0);
                    }
                }

                // write original archive filepath
                if (!String.IsNullOrEmpty(ArchiveName))
                {
                    byte[] archiveNameBytes = Util.ShiftJISEncoding.GetBytes(ArchiveName);
                    f.Write(archiveNameBytes, 0, archiveNameBytes.Length);
                    f.WriteByte(0);
                }

                // fix up header if we inserted metadata
                if (ContainsFileMetadata && metadata != null)
                {
                    long pos = f.Position;

                    FirstFileStart = ((uint)(f.Position)).Align(Alignment);
                    f.Position     = 0xC;
                    f.WriteUInt32(FirstFileStart.SwapEndian());

                    ptr = FirstFileStart;
                    for (int i = 0; i < files.Length; ++i)
                    {
                        f.Position = 0x1C + (i * EntrySize);
                        var fi = new System.IO.FileInfo(files[i]);
                        if (ContainsStartPointers)
                        {
                            f.WriteUInt32(ptr.SwapEndian());
                        }
                        if (ContainsEndPointers)
                        {
                            f.WriteUInt32(((uint)(fi.Length.Align((int)Alignment))).SwapEndian());
                        }
                        ptr += (uint)fi.Length.Align((int)Alignment);
                    }
                    f.Position = 0x1C + (files.Length * EntrySize);
                    f.WriteUInt32(ptr.SwapEndian());

                    f.Position = pos;
                }

                // pad until files
                if (infile != null)
                {
                    infile.Position = f.Position;
                    for (long i = f.Length; i < FirstFileStart; ++i)
                    {
                        f.WriteByte((byte)infile.ReadByte());
                    }
                }
                else
                {
                    for (long i = f.Length; i < FirstFileStart; ++i)
                    {
                        f.WriteByte(0);
                    }
                }

                // actually write files
                for (int i = 0; i < files.Length; ++i)
                {
                    using (var fs = new System.IO.FileStream(files[i], FileMode.Open)) {
                        Console.WriteLine("Packing #" + i.ToString("D4") + ": " + files[i]);
                        Util.CopyStream(fs, f, (int)fs.Length);
                        while (f.Length % Alignment != 0)
                        {
                            f.WriteByte(0);
                        }
                    }
                }
            }
        }
示例#15
0
        public void Save(Stream stream)
        {
            // Calculate IndexSize
            FileData.IndexCount = (uint)Files.Count;
            FileData.IndexSize  = (FileData.IndexCount * 0x1C);

            // Write Names & calculate NamesSize
            Dictionary <string, uint> filenames = new Dictionary <string, uint>(StringComparer.InvariantCultureIgnoreCase);

            stream.Seek(CalculateEntryNamesOffset(), SeekOrigin.Begin);
            uint filenameOffset = 0;

            foreach (PackfileEntry entry in Files)
            {
                string filename = Path.GetFileNameWithoutExtension(entry.Name);

                if (filenames.ContainsKey(filename))
                {
                    entry.Data.FilenameOffset = filenames[filename];
                }
                else
                {
                    entry.Data.FilenameOffset = filenameOffset;
                    int length = stream.WriteAsciiNullTerminatedString(filename);
                    filenames.Add(filename, filenameOffset);
                    filenameOffset += (uint)length;
                }
            }
            FileData.NamesSize = filenameOffset;

            // Write Extensions & calculate ExtensionsSize
            Dictionary <string, uint> extensions = new Dictionary <string, uint>(StringComparer.InvariantCultureIgnoreCase);
            uint extensionOffset = 0;

            stream.Seek(CalculateExtensionsOffset(), SeekOrigin.Begin);
            foreach (PackfileEntry entry in Files)
            {
                string extension = Path.GetExtension(entry.Name);
                if (extension.StartsWith("."))
                {
                    extension = extension.Remove(0, 1);
                }

                if (extensions.ContainsKey(extension))
                {
                    entry.Data.ExtensionOffset = extensions[extension];
                }
                else
                {
                    entry.Data.ExtensionOffset = extensionOffset;
                    int length = stream.WriteAsciiNullTerminatedString(extension);
                    extensions.Add(extension, extensionOffset);
                    extensionOffset += (uint)length;
                }
            }
            FileData.ExtensionsSize = extensionOffset;

            // Write data
            uint dataOffset = 0;

            stream.Seek(CalculateDataStartOffset(), SeekOrigin.Begin);
            foreach (PackfileEntry entry in Files)
            {
                Stream inStream = m_Streams[entry.Name];
                entry.Data.Size           = (uint)inStream.Length;
                entry.Data.Start          = dataOffset;
                entry.Data.CompressedSize = (uint)0xFFFFFFFF;
                inStream.CopyTo(stream);
                dataOffset += entry.Data.Size;
                stream.Align(16);
                dataOffset = dataOffset.Align(16);
            }

            // Write Header
            stream.Seek(0, SeekOrigin.Begin);
            FileData.Descriptor           = 0x51890ACE;
            FileData.Version              = 0x04;
            FileData.CompressedDataSize   = 0xFFFFFFFF;
            FileData.UncompressedDataSize = dataOffset;
            FileData.PackageSize          = (uint)stream.Length;
            stream.WriteStruct(FileData);

            // Write file index
            stream.Seek(GetEntryDataOffset(), SeekOrigin.Begin);
            foreach (PackfileEntry entry in Files)
            {
                stream.WriteStruct(entry.Data);
            }
        }
        public void Build()
        {
            // Build tasks
            foreach (var task in _tasks)
            {
                task.Build();
            }

            // Remove empty sections
            for (int i = _sections.Count - 1; i >= 0; i--)
            {
                if (_sections[i].Blobs.Count == 0)
                {
                    _sections.RemoveAt(i);
                }
            }

            // Init
            if (_is32Bits)
            {
                _sizeOfHeaders = (uint)
                                 (
                    PEConstants.DosHeaderSize +
                    PEConstants.DosX86Stub.Length +
                    4 +                             // NTSignature
                    PEConstants.COFFHeaderSize +
                    PEConstants.PEHeaderSize +
                    PEConstants.NumberOfRvaAndSizes * 8 +
                    PEConstants.SectionHeaderSize * _sections.Count
                                 );
            }
            else
            {
                _sizeOfHeaders = (uint)
                                 (
                    PEConstants.DosHeaderSize +
                    PEConstants.DosX86Stub.Length +
                    4 +                             // NTSignature
                    PEConstants.COFFHeaderSize +
                    PEConstants.PEHeader64Size +
                    PEConstants.NumberOfRvaAndSizes * 8 +
                    PEConstants.SectionHeaderSize * _sections.Count
                                 );
            }

            _sizeOfHeaders           = _sizeOfHeaders.Align(_fileAlignment);
            _sizeOfImage             = _sizeOfHeaders;
            _baseOfCode              = 0;
            _baseOfData              = 0;
            _sizeOfCode              = 0;
            _sizeOfInitializedData   = 0;
            _sizeOfUninitializedData = 0;

            if (_is32Bits)
            {
                _characteristics |= ImageCharacteristics.MACHINE_32BIT;
            }
            else
            {
                _characteristics &= ~ImageCharacteristics.MACHINE_32BIT;
            }

            // Build sections
            uint virtualAddress   = _sectionAlignment;
            uint pointerToRawData = _sizeOfHeaders;

            foreach (BuildSection section in _sections)
            {
                section._rva = virtualAddress;
                section._pointerToRawData = pointerToRawData;

                uint blobLength = 0;
                foreach (var blob in section.Blobs)
                {
                    if (blob.OffsetAlignment > 0)
                    {
                        blobLength = blobLength.Align(blob.OffsetAlignment);
                    }

                    blob._rva = virtualAddress + blobLength;
                    blob._pointerToRawData = pointerToRawData + blobLength;
                    blobLength            += (uint)blob.Length;
                }

                section._virtualSize   = blobLength;
                section._sizeOfRawData = blobLength.Align(_fileAlignment);

                virtualAddress   += section.VirtualSize.Align(_sectionAlignment);
                pointerToRawData += section.SizeOfRawData;

                if ((section.Characteristics & SectionCharacteristics.ContainsCode) != 0)
                {
                    _sizeOfCode += section.SizeOfRawData;
                    if (_baseOfCode == 0)
                    {
                        _baseOfCode = section.RVA;
                    }
                }

                if ((section.Characteristics & SectionCharacteristics.ContainsInitializedData) != 0)
                {
                    _sizeOfInitializedData += section.SizeOfRawData;
                    if (_baseOfData == 0)
                    {
                        _baseOfData = section.RVA;
                    }
                }

                if ((section.Characteristics & SectionCharacteristics.ContainsUninitializedData) != 0)
                {
                    _sizeOfUninitializedData += section.SizeOfRawData;
                }
            }

            _sizeOfImage = virtualAddress;

            // Apply fixups
            foreach (var fixup in _fixups)
            {
                fixup.ApplyFixup();
            }
        }