// Write 1 file to a zip archive
using System.IO.Compression;
using System.IO.Hashing;
// Zip data
var filename = "bananas.txt"u8.ToArray();
var filedata = "BANANABANANABANANABANANABANANA"u8.ToArray();
var fileComment = "file comment"u8.ToArray();
var comment = "zip comment"u8.ToArray();
var (date, time) = ToDos(DateTime.UtcNow);
// Compress the data
using var outputStream = new MemoryStream();
using (var deflateStream = new DeflateStream(outputStream, CompressionLevel.Optimal))
{
deflateStream.Write(filedata);
}
var data = outputStream.ToArray();
// Create ZIP
using var zip = File.Create("minimal.zip");
using var output = new BinaryWriter(zip);
// Calculate CRC-32/ISO-HDLC over the uncompressed data
var crc = new Crc32();
crc.Append(filedata);
var crc32 = crc.GetCurrentHashAsUInt32();
// Write file
var offset = WriteLocalFileHeader();
WriteData();
// Write directory
var info = WriteCentralDirectoryHeader(offset);
WriteEndOfCentralDirectoryRecord(info.Position, info.Size);
long WriteLocalFileHeader()
{
var currentPosition = output.BaseStream.Position;
output.Write("PK\x03\x04".AsSpan());
output.Write((ushort)(2 * 10)); // minimal version to extract
output.Write((ushort)0); // general purpose bit flag
output.Write((ushort)8); // compression method (0 = none, 8 = deflate)
output.Write(time);
output.Write(date);
output.Write(crc32);
output.Write((uint)data.Length); // compressed size
output.Write((uint)filedata.Length); // uncompressed size
output.Write((ushort)filename.Length);
output.Write((ushort)0); // extra field length
output.Write(filename);
output.Write(Array.Empty<byte>()); // extra field
return currentPosition;
}
void WriteData() => output.Write(data);
const ushort diskIndex = 0;
const ushort fileCount = 1;
(long Position, long Size) WriteCentralDirectoryHeader(long localHeaderOffset)
{
var currentPosition = output.BaseStream.Position;
output.Write("PK\x01\x02".AsSpan());
output.Write((ushort)(2 * 10)); // version made by
output.Write((ushort)(2 * 10)); // version needed to extract
output.Write((ushort)0); // general purpose bit flag
output.Write((ushort)8); // compression method (0 = none, 8 = deflate)
output.Write(time);
output.Write(date);
output.Write(crc32);
output.Write((uint)data.Length); // compressed size
output.Write((uint)filedata.Length); // uncompressed size
output.Write((ushort)filename.Length);
output.Write((ushort)0); // extra field length
output.Write((ushort)fileComment.Length);
output.Write(diskIndex);
output.Write((ushort)0); // internal file attributes (0 = binary)
output.Write((uint)0b00100000); // external file attributes (MS-DOS™ archive)
output.Write((uint)localHeaderOffset);
output.Write(filename);
output.Write(Array.Empty<byte>()); // extra field
output.Write(fileComment);
return (currentPosition, output.BaseStream.Position - currentPosition);
}
void WriteEndOfCentralDirectoryRecord(long centralDirectoryOffset, long size)
{
output.Write("PK\x05\x06".AsSpan());
output.Write(diskIndex); // current disk number
output.Write(diskIndex); // disk where central directory starts
output.Write(fileCount); // number of central directory records on this disk
output.Write(fileCount); // total central directory records
output.Write((uint)size); // total central directory records in bytes
output.Write((uint)centralDirectoryOffset);
output.Write((uint)comment.Length);
output.Write(comment);
}
// dates and times are in MS-DOS™ format
(ushort Date, ushort Time)ToDos(DateTime datetime) => (
(ushort)((datetime.Year - 1980) << 9 | datetime.Month << 5 | datetime.Day),
(ushort)(datetime.Hour << 11 | datetime.Minute << 5 | datetime.Second >> 1));