Example #1
0
        // Create a new archive with the files specified in filesToPack
        public static void CreateArchive(string volumeFilename, IEnumerable <string> filesToPack)
        {
            List <string> filesToPackSorted = new List <string>(filesToPack);

            // Sort files alphabetically based on the filename only (not including the full path).
            // Packed files must be locatable by a binary search of their filename.
            filesToPackSorted.Sort(new System.Comparison <string>((s1, s2) => Path.GetFileName(s1).CompareTo(Path.GetFileName(s2))));

            CreateVolumeInfo volInfo = new CreateVolumeInfo();

            volInfo.filesToPack = filesToPackSorted;
            volInfo.names       = GetNamesFromPaths(filesToPackSorted);

            // Allowing duplicate names when packing may cause unintended results during binary search and file extraction.
            VerifySortedContainerHasNoDuplicateNames(volInfo.names);

            // Open input files and prepare header and indexing info
            PrepareHeader(volInfo, volumeFilename);

            WriteVolume(volumeFilename, volInfo);

            // Dispose of open readers
            foreach (BinaryReader reader in volInfo.fileStreamReaders)
            {
                reader.Dispose();
            }
        }
Example #2
0
        private static void PrepareHeader(CreateVolumeInfo volInfo, string volumeFilename)
        {
            OpenAllInputFiles(volInfo, volumeFilename);

            volInfo.stringTableLength = 0;

            // Get file sizes and calculate length of string table
            for (int i = 0; i < volInfo.fileCount(); ++i)
            {
                IndexEntry indexEntry = new IndexEntry();

                long fileSize = volInfo.fileStreamReaders[i].BaseStream.Length;
                if (fileSize > uint.MaxValue)
                {
                    throw new System.Exception("File " + volInfo.filesToPack[i] +
                                               " is too large to fit inside a volume archive. Writing volume " + volumeFilename + " aborted.");
                }

                indexEntry.fileSize        = (int)(fileSize);
                indexEntry.filenameOffset  = volInfo.stringTableLength;
                indexEntry.compressionType = CompressionType.Uncompressed;

                volInfo.indexEntries.Add(indexEntry);

                // Add length of internal filename plus null terminator to string table length.
                if (volInfo.stringTableLength + (ulong)volInfo.names[i].Length + 1 > uint.MaxValue)
                {
                    throw new System.Exception("String table length is too long to create volume " + volumeFilename);
                }

                volInfo.stringTableLength += (uint)(volInfo.names[i].Length) + 1;
            }

            // Calculate size of index table
            if ((ulong)volInfo.fileCount() * IndexEntry.SizeInBytes > uint.MaxValue)
            {
                throw new System.Exception("Index table length is too long to create volume " + volumeFilename);
            }
            volInfo.indexTableLength = (uint)volInfo.fileCount() * IndexEntry.SizeInBytes;

            // Calculate the zero padded length of the string table and index table
            volInfo.paddedStringTableLength = (uint)((volInfo.stringTableLength + 7) & ~3);
            volInfo.paddedIndexTableLength  = (uint)((volInfo.indexTableLength + 3) & ~3);

            if (volInfo.indexEntries.Count == 0)
            {
                return;
            }

            volInfo.indexEntries[0].dataBlockOffset = volInfo.paddedStringTableLength + volInfo.paddedIndexTableLength + 32;

            // Calculate offsets to the files
            for (int i = 1; i < volInfo.fileCount(); ++i)
            {
                IndexEntry previousIndex = volInfo.indexEntries[i - 1];
                volInfo.indexEntries[i].dataBlockOffset = (uint)((previousIndex.dataBlockOffset + previousIndex.fileSize + 11) & ~3);
            }
        }
Example #3
0
        private static void OpenAllInputFiles(CreateVolumeInfo volInfo, string volumeFilename)
        {
            volInfo.fileStreamReaders.Clear();

            foreach (string filename in volInfo.filesToPack)
            {
                try {
                    FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                    volInfo.fileStreamReaders.Add(new BinaryReader(fs));
                }
                catch (System.Exception e) {
                    throw new System.Exception("Error attempting to open " + filename +
                                               " for reading into volume " + volumeFilename + ". Internal Error: " + e);
                }
            }
        }
Example #4
0
        private static void WriteVolume(string filename, CreateVolumeInfo volInfo)
        {
            foreach (string path in volInfo.filesToPack)
            {
                if (string.Compare(filename, path, true) == 0)                 // NOTE: Case insensitive comparison
                {
                    throw new System.Exception("Cannot include a volume being overwritten in new volume " + filename);
                }
            }

            using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None))
                using (BinaryWriter volWriter = new BinaryWriter(fs))
                {
                    WriteHeader(volWriter, volInfo);
                    WriteFiles(volWriter, volInfo);
                }
        }
Example #5
0
        private static void WriteHeader(BinaryWriter volWriter, CreateVolumeInfo volInfo)
        {
            // Write the header
            new SectionHeader(TagVOL_, volInfo.paddedStringTableLength + volInfo.paddedIndexTableLength + 24).Serialize(volWriter);

            new SectionHeader(TagVOLH, 0).Serialize(volWriter);

            // Write the string table
            new SectionHeader(TagVOLS, volInfo.paddedStringTableLength).Serialize(volWriter);

            volWriter.Write(volInfo.stringTableLength);

            // Write out all internal file name strings (including NULL terminator)
            for (int i = 0; i < volInfo.fileCount(); ++i)
            {
                // Account for the null terminator in the size.
                byte[] stringBuffer = System.Text.Encoding.ASCII.GetBytes(volInfo.names[i] + "\0");
                volWriter.Write(stringBuffer, 0, stringBuffer.Length);
            }

            byte[] padding = new byte[4];             // Pad with 0 bytes
            volWriter.Write(padding, 0, (int)(volInfo.paddedStringTableLength - (volInfo.stringTableLength + 4)));

            // Write the index table
            new SectionHeader(TagVOLI, volInfo.indexTableLength).Serialize(volWriter);

            int bytesWritten = 0;

            foreach (IndexEntry entry in volInfo.indexEntries)
            {
                if (bytesWritten >= volInfo.indexTableLength)
                {
                    break;
                }

                entry.Serialize(volWriter);
                bytesWritten += IndexEntry.SizeInBytes;
            }

            volWriter.Write(padding, 0, (int)(volInfo.paddedIndexTableLength - volInfo.indexTableLength));
        }
Example #6
0
        private static void WriteFiles(BinaryWriter volWriter, CreateVolumeInfo volInfo)
        {
            // Write each file header and contents
            for (int i = 0; i < volInfo.fileCount(); ++i)
            {
                new SectionHeader(TagVBLK, (uint)volInfo.indexEntries[i].fileSize).Serialize(volWriter);

                try {
                    volInfo.fileStreamReaders[i].BaseStream.Seek(0, SeekOrigin.Begin);
                    volWriter.Write(volInfo.fileStreamReaders[i].ReadBytes((int)volInfo.fileStreamReaders[i].BaseStream.Length));

                    // Add padding after the file, ensuring it ends on a 4 byte boundary
                    // Use a bitmask to quickly calculate the modulo 4 (remainder) of fileSize
                    byte[] padding = new byte[4];
                    volWriter.Write(padding, 0, (-volInfo.indexEntries[i].fileSize) & 3);
                }
                catch (System.Exception e) {
                    throw new System.Exception("Unable to pack file " + volInfo.names[i] + ". Internal error: " + e);
                }
            }
        }