// 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(); } }
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); } }
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); } } }
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); } }
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)); }
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); } } }