internal static PackagedFileInfo CreateFromEntry(FileEntry13 entry, Stream dataStream)
        {
            var info = new PackagedFileInfo
            {
                PackageStream    = dataStream,
                OffsetInFile     = entry.OffsetInFile,
                SizeOnDisk       = entry.SizeOnDisk,
                UncompressedSize = entry.UncompressedSize,
                ArchivePart      = entry.ArchivePart,
                Flags            = entry.Flags,
                Crc   = entry.Crc,
                Solid = false
            };

            int nameLen;

            for (nameLen = 0; nameLen < entry.Name.Length && entry.Name[nameLen] != 0; nameLen++)
            {
            }
            info.Name = Encoding.UTF8.GetString(entry.Name, 0, nameLen);

            uint compressionMethod = entry.Flags & 0x0F;

            if (compressionMethod > 2 || (entry.Flags & ~0x7F) != 0)
            {
                string msg = $"File '{info.Name}' has unsupported flags: {entry.Flags}";
                throw new InvalidDataException(msg);
            }

            return(info);
        }
Example #2
0
        internal static PackagedFileInfo CreateFromEntry(FileEntry13 entry, Stream dataStream)
        {
            var info = new PackagedFileInfo();

            info.PackageStream = dataStream;

            var nameLen = 0;

            for (nameLen = 0; nameLen < entry.Name.Length && entry.Name[nameLen] != 0; nameLen++)
            {
            }
            info.Name = Encoding.UTF8.GetString(entry.Name, 0, nameLen);

            var compressionMethod = entry.Flags & 0x0F;

            if (compressionMethod > 2 || (entry.Flags & ~0x7F) != 0)
            {
                var msg = String.Format("File '{0}' has unsupported flags: {1}", info.Name, entry.Flags);
                throw new InvalidDataException(msg);
            }

            info.OffsetInFile     = entry.OffsetInFile;
            info.SizeOnDisk       = entry.SizeOnDisk;
            info.UncompressedSize = entry.UncompressedSize;
            info.ArchivePart      = entry.ArchivePart;
            info.Flags            = entry.Flags;
            info.Crc = entry.Crc;
            return(info);
        }
        internal static PackagedFileInfo CreateSolidFromEntry(FileEntry13 entry, Stream dataStream, uint solidOffset, Stream solidStream)
        {
            var info = CreateFromEntry(entry, dataStream);

            info.Solid       = true;
            info.SolidOffset = solidOffset;
            info.SolidStream = solidStream;
            return(info);
        }
Example #4
0
        public void WriteV10(FileStream mainStream)
        {
            using (var writer = new BinaryWriter(mainStream, new UTF8Encoding(), true))
            {
                var header = new LSPKHeader10
                {
                    Version      = (uint)Version,
                    NumFiles     = (UInt32)_package.Files.Count,
                    FileListSize = (UInt32)(Marshal.SizeOf(typeof(FileEntry13)) * _package.Files.Count)
                };
                header.DataOffset = (UInt32)Marshal.SizeOf(typeof(LSPKHeader10)) + 4 + header.FileListSize;
                int paddingLength = PaddingLength();
                if (header.DataOffset % paddingLength > 0)
                {
                    header.DataOffset += (UInt32)(paddingLength - header.DataOffset % paddingLength);
                }

                // Write a placeholder instead of the actual headers; we'll write them after we
                // compressed and flushed all files to disk
                var placeholder = new byte[header.DataOffset];
                writer.Write(placeholder);

                long totalSize    = _package.Files.Sum(p => (long)p.Size());
                long currentSize  = 0;
                var  writtenFiles = new List <PackagedFileInfo>();
                foreach (AbstractFileInfo file in _package.Files)
                {
                    WriteProgress(file, currentSize, totalSize);
                    writtenFiles.Add(WriteFile(file));
                    currentSize += file.Size();
                }

                mainStream.Seek(0, SeekOrigin.Begin);
                writer.Write(Package.Signature);
                header.NumParts = (UInt16)_streams.Count;
                header.Priority = _package.Metadata.Priority;
                header.Flags    = (byte)_package.Metadata.Flags;
                BinUtils.WriteStruct(writer, ref header);

                foreach (PackagedFileInfo file in writtenFiles)
                {
                    FileEntry13 entry = file.MakeEntryV13();
                    if (entry.ArchivePart == 0)
                    {
                        entry.OffsetInFile -= header.DataOffset;
                    }

                    // v10 packages don't support compression level in the flags field
                    entry.Flags &= 0x0f;
                    BinUtils.WriteStruct(writer, ref entry);
                }
            }
        }
Example #5
0
        public void WriteV13(FileStream mainStream)
        {
            long totalSize   = _package.Files.Sum(p => (long)p.Size());
            long currentSize = 0;

            var writtenFiles = new List <PackagedFileInfo>();

            foreach (AbstractFileInfo file in _package.Files)
            {
                WriteProgress(file, currentSize, totalSize);
                writtenFiles.Add(WriteFile(file));
                currentSize += file.Size();
            }

            using (var writer = new BinaryWriter(mainStream, new UTF8Encoding(), true))
            {
                var header = new LSPKHeader13
                {
                    Version        = (uint)Version,
                    FileListOffset = (UInt32)mainStream.Position
                };

                writer.Write((UInt32)writtenFiles.Count);

                var fileList       = new MemoryStream();
                var fileListWriter = new BinaryWriter(fileList);
                foreach (PackagedFileInfo file in writtenFiles)
                {
                    FileEntry13 entry = file.MakeEntryV13();
                    BinUtils.WriteStruct(fileListWriter, ref entry);
                }

                byte[] fileListBuf = fileList.ToArray();
                fileListWriter.Dispose();
                byte[] compressedFileList = LZ4Codec.EncodeHC(fileListBuf, 0, fileListBuf.Length);

                writer.Write(compressedFileList);

                header.FileListSize = (UInt32)mainStream.Position - header.FileListOffset;
                header.NumParts     = (UInt16)_streams.Count;
                header.Priority     = _package.Metadata.Priority;
                header.Flags        = (byte)_package.Metadata.Flags;
                header.Md5          = ComputeArchiveHash();
                BinUtils.WriteStruct(writer, ref header);

                writer.Write((UInt32)(8 + Marshal.SizeOf(typeof(LSPKHeader13))));
                writer.Write(Package.Signature);
            }
        }
Example #6
0
        private Package ReadPackageV13(FileStream mainStream, BinaryReader reader)
        {
            var package = new Package();
            var header  = BinUtils.ReadStruct <LSPKHeader13>(reader);

            if (header.Version != (ulong)Package.CurrentVersion)
            {
                string msg = $"Unsupported package version {header.Version}; this extractor only supports {Package.CurrentVersion}";
                throw new InvalidDataException(msg);
            }

            package.Metadata.Flags    = (PackageFlags)header.Flags;
            package.Metadata.Priority = header.Priority;

            if (_metadataOnly)
            {
                return(package);
            }

            OpenStreams(mainStream, header.NumParts);
            mainStream.Seek(header.FileListOffset, SeekOrigin.Begin);
            int numFiles       = reader.ReadInt32();
            int fileBufferSize = Marshal.SizeOf(typeof(FileEntry13)) * numFiles;

            byte[] compressedFileList = reader.ReadBytes((int)header.FileListSize - 4);

            var uncompressedList = new byte[fileBufferSize];
            int uncompressedSize = LZ4Codec.Decode(compressedFileList, 0, compressedFileList.Length, uncompressedList, 0, fileBufferSize, true);

            if (uncompressedSize != fileBufferSize)
            {
                string msg = $"LZ4 compressor disagrees about the size of file headers; expected {fileBufferSize}, got {uncompressedSize}";
                throw new InvalidDataException(msg);
            }

            var ms  = new MemoryStream(uncompressedList);
            var msr = new BinaryReader(ms);

            var entries = new FileEntry13[numFiles];

            BinUtils.ReadStructs(msr, entries);
            foreach (var entry in entries)
            {
                package.Files.Add(PackagedFileInfo.CreateFromEntry(entry, _streams[entry.ArchivePart]));
            }

            return(package);
        }
Example #7
0
        internal FileEntry13 MakeEntryV13()
        {
            var entry = new FileEntry13();

            entry.Name = new byte[256];
            var encodedName = Encoding.UTF8.GetBytes(Name.Replace('\\', '/'));

            Array.Copy(encodedName, entry.Name, encodedName.Length);

            entry.OffsetInFile     = OffsetInFile;
            entry.SizeOnDisk       = SizeOnDisk;
            entry.UncompressedSize = ((Flags & 0x0F) == 0) ? 0 : UncompressedSize;
            entry.ArchivePart      = ArchivePart;
            entry.Flags            = Flags;
            entry.Crc = Crc;
            return(entry);
        }
Example #8
0
        private Package ReadPackageV13(FileStream mainStream, BinaryReader reader)
        {
            var package = new Package();
            var header  = BinUtils.ReadStruct <LSPKHeader13>(reader);

            if (header.Version != (ulong)PackageVersion.V13)
            {
                string msg = $"Unsupported package version {header.Version}; this package layout is only supported for {PackageVersion.V13}";
                throw new InvalidDataException(msg);
            }

            package.Metadata.Flags    = (PackageFlags)header.Flags;
            package.Metadata.Priority = header.Priority;
            package.Version           = PackageVersion.V13;

            if (_metadataOnly)
            {
                return(package);
            }

            OpenStreams(mainStream, header.NumParts);
            mainStream.Seek(header.FileListOffset, SeekOrigin.Begin);
            int numFiles       = reader.ReadInt32();
            int fileBufferSize = Marshal.SizeOf(typeof(FileEntry13)) * numFiles;

            byte[] compressedFileList = reader.ReadBytes((int)header.FileListSize - 4);

            var uncompressedList = new byte[fileBufferSize];
            int uncompressedSize = LZ4Codec.Decode(compressedFileList, 0, compressedFileList.Length, uncompressedList, 0, fileBufferSize, true);

            if (uncompressedSize != fileBufferSize)
            {
                string msg = $"LZ4 compressor disagrees about the size of file headers; expected {fileBufferSize}, got {uncompressedSize}";
                throw new InvalidDataException(msg);
            }

            var ms  = new MemoryStream(uncompressedList);
            var msr = new BinaryReader(ms);

            var entries = new FileEntry13[numFiles];

            BinUtils.ReadStructs(msr, entries);

            if ((package.Metadata.Flags & PackageFlags.Solid) == PackageFlags.Solid && numFiles > 0)
            {
                // Calculate compressed frame offset and bounds
                uint totalUncompressedSize = 0;
                uint totalSizeOnDisk       = 0;
                uint firstOffset           = 0xffffffff;
                uint lastOffset            = 0;

                foreach (var entry in entries)
                {
                    totalUncompressedSize += entry.UncompressedSize;
                    totalSizeOnDisk       += entry.SizeOnDisk;
                    if (entry.OffsetInFile < firstOffset)
                    {
                        firstOffset = entry.OffsetInFile;
                    }
                    if (entry.OffsetInFile + entry.SizeOnDisk > lastOffset)
                    {
                        lastOffset = entry.OffsetInFile + entry.SizeOnDisk;
                    }
                }

                if (firstOffset != 7 || lastOffset - firstOffset != totalSizeOnDisk)
                {
                    string msg = $"Incorrectly compressed solid archive; offsets {firstOffset}/{lastOffset}, bytes {totalSizeOnDisk}";
                    throw new InvalidDataException(msg);
                }

                // Decompress all files as a single frame (solid)
                byte[] frame = new byte[lastOffset];
                mainStream.Seek(0, SeekOrigin.Begin);
                mainStream.Read(frame, 0, (int)lastOffset);

                byte[] decompressed       = Native.LZ4FrameCompressor.Decompress(frame);
                var    decompressedStream = new MemoryStream(decompressed);

                // Update offsets to point to the decompressed chunk
                uint offset           = 7;
                uint compressedOffset = 0;
                foreach (var entry in entries)
                {
                    if (entry.OffsetInFile != offset)
                    {
                        throw new InvalidDataException("File list in solid archive not contiguous");
                    }

                    var file = PackagedFileInfo.CreateSolidFromEntry(entry, _streams[entry.ArchivePart], compressedOffset, decompressedStream);
                    package.Files.Add(file);

                    offset           += entry.SizeOnDisk;
                    compressedOffset += entry.UncompressedSize;
                }
            }
            else
            {
                foreach (var entry in entries)
                {
                    package.Files.Add(PackagedFileInfo.CreateFromEntry(entry, _streams[entry.ArchivePart]));
                }
            }

            return(package);
        }
Example #9
0
        internal FileEntry13 MakeEntryV13()
        {
            var entry = new FileEntry13();
            entry.Name = new byte[256];
            var encodedName = Encoding.UTF8.GetBytes(Name.Replace('\\', '/'));
            Array.Copy(encodedName, entry.Name, encodedName.Length);

            entry.OffsetInFile = OffsetInFile;
            entry.SizeOnDisk = SizeOnDisk;
            entry.UncompressedSize = ((Flags & 0x0F) == 0) ? 0 : UncompressedSize;
            entry.ArchivePart = ArchivePart;
            entry.Flags = Flags;
            entry.Crc = Crc;
            return entry;
        }
Example #10
0
        internal static PackagedFileInfo CreateFromEntry(FileEntry13 entry, Stream dataStream)
        {
            var info = new PackagedFileInfo();
            info.PackageStream = dataStream;

            var nameLen = 0;
            for (nameLen = 0; nameLen < entry.Name.Length && entry.Name[nameLen] != 0; nameLen++) { }
            info.Name = Encoding.UTF8.GetString(entry.Name, 0, nameLen);

            var compressionMethod = entry.Flags & 0x0F;
            if (compressionMethod > 2 || (entry.Flags & ~0x7F) != 0)
            {
                var msg = String.Format("File '{0}' has unsupported flags: {1}", info.Name, entry.Flags);
                throw new InvalidDataException(msg);
            }

            info.OffsetInFile = entry.OffsetInFile;
            info.SizeOnDisk = entry.SizeOnDisk;
            info.UncompressedSize = entry.UncompressedSize;
            info.ArchivePart = entry.ArchivePart;
            info.Flags = entry.Flags;
            info.Crc = entry.Crc;
            return info;
        }