joriszwart.nl

I code dreams™

Zip2.cs

View Download

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

View Download