// Write 1 file to a zip archive using System.IO.Compression; using System.IO.Hashing; // Zip data var filename = "file.txt"u8.ToArray(); var filedata = "The quick brown fox jumps over the lazy dog."u8.ToArray(); var fileComment = "file comment"u8.ToArray(); var comment = "zip comment"u8.ToArray(); var (date, time) = ToDos(DateTime.UtcNow); // Compress the data (TODO check pkzip 2.04g compatibility) using var outputStream = new MemoryStream(); using (var deflateStream = new DeflateStream(outputStream, CompressionLevel.Optimal)) { deflateStream.Write(filedata); } var data = outputStream.ToArray(); const ushort diskIndex = 0; const ushort fileCount = 1; // 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()); // extra field return currentPosition; } void WriteData() => output.Write(data); (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()); // 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));