Task 857: .ZIP File Format

Task 857: .ZIP File Format

1. Properties of the .ZIP File Format

The properties remain as previously detailed, encompassing all structural elements from local file headers through to the end-of-central-directory record, including ZIP64 extensions.

The links remain as provided.

3. HTML JavaScript for Drag-and-Drop .ZIP Property Dumper

The HTML/JavaScript snippet is already complete as presented, with full parsing logic for properties. No upgrades are required here.

4. Python Class for .ZIP Handling

The following is the upgraded Python class, with the omitted parsing logic fully implemented (extending local header parsing to include central directory, ZIP64 records, end-of-central-directory, and handling extra fields, comments, and data descriptors comprehensively). The write method now implements a basic functionality to create a new .ZIP file containing one sample file (an empty text file), demonstrating encoding of headers. Error handling and ZIP64 support in writing are basic; extend for production use.

ZIP Property Dumper
Drag and drop a .ZIP file here

This implementation assumes no encryption, spanning, or advanced features for simplicity. Timestamps are placeholders in write.

5. Java Class for .ZIP Handling

The following is the upgraded Java class, with complete parsing logic mirroring the Python implementation (using ByteBuffer for little-endian unpacking). The write method creates a new .ZIP file with one sample file.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.zip.CRC32;

public class ZipHandler {
    private String filepath;
    private Map<String, Object> properties = new HashMap<>();

    public ZipHandler(String filepath) {
        this.filepath = filepath;
        parse();
    }

    private void parse() {
        try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
            FileChannel channel = raf.getChannel();
            long size = channel.size();
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            properties.put("local_files", new ArrayList<Map<String, Object>>());
            properties.put("central_directories", new ArrayList<Map<String, Object>>());
            properties.put("zip64_eocd", null);
            properties.put("zip64_locator", null);
            properties.put("eocd", new HashMap<String, Object>());

            // Find EOCD
            int eocdOffset = findEOCD(buffer, (int) size);
            if (eocdOffset == -1) throw new IOException("Invalid ZIP: No EOCD");

            // Parse EOCD
            parseEOCD(buffer, eocdOffset);

            // Parse Zip64 if present
            if ((boolean) ((Map<String, Object>) properties.get("eocd")).get("zip64_present")) {
                parseZip64(buffer, eocdOffset);
            }

            // Parse Central Directory
            long cdOffset = (long) ((Map<String, Object>) properties.get("eocd")).get("cd_offset");
            List<Map<String, Object>> cds = (List<Map<String, Object>>) properties.get("central_directories");
            int totalEntries = (int) ((Map<String, Object>) properties.get("eocd")).get("total_entries");
            for (int i = 0; i < totalEntries; i++) {
                Map<String, Object> cdProps = parseCentralHeader(buffer, (int) cdOffset);
                cds.add(cdProps);
                cdOffset += (int) cdProps.get("header_size");
            }

            // Parse Local Headers
            int offset = 0;
            List<Map<String, Object>> locals = (List<Map<String, Object>>) properties.get("local_files");
            for (int i = 0; i < totalEntries; i++) {
                Map<String, Object> localProps = parseLocalHeader(buffer, offset);
                locals.add(localProps);
                offset += (int) localProps.get("header_size") + (int) localProps.get("compressed_size");
                if ((int) localProps.get("general_flag") & 0x08) {
                    offset += 12; // Basic data descriptor
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private int findEOCD(ByteBuffer buffer, int size) {
        for (int i = size - 22; i >= 0; i--) {
            if (buffer.getInt(i) == 0x06054b50) return i;
        }
        return -1;
    }

    private void parseEOCD(ByteBuffer buffer, int offset) {
        Map<String, Object> eocd = (Map<String, Object>) properties.get("eocd");
        int sig = buffer.getInt(offset);
        short diskNum = buffer.getShort(offset + 4);
        short startDisk = buffer.getShort(offset + 6);
        short entriesDisk = buffer.getShort(offset + 8);
        short totalEntries = buffer.getShort(offset + 10);
        int cdSize = buffer.getInt(offset + 12);
        int cdOffset = buffer.getInt(offset + 16);
        short commentLen = buffer.getShort(offset + 20);
        eocd.put("signature", Integer.toHexString(sig));
        eocd.put("disk_number", Short.toUnsignedInt(diskNum));
        eocd.put("start_disk", Short.toUnsignedInt(startDisk));
        eocd.put("entries_on_disk", Short.toUnsignedInt(entriesDisk));
        eocd.put("total_entries", Short.toUnsignedInt(totalEntries));
        eocd.put("cd_size", cdSize);
        eocd.put("cd_offset", cdOffset);
        eocd.put("comment_length", Short.toUnsignedInt(commentLen));
        byte[] commentBytes = new byte[commentLen];
        buffer.position(offset + 22);
        buffer.get(commentBytes);
        eocd.put("comment", new String(commentBytes));
        eocd.put("zip64_present", totalEntries == 0xFFFF || cdSize == 0xFFFFFFFF || cdOffset == 0xFFFFFFFF);
    }

    private void parseZip64(ByteBuffer buffer, int eocdOffset) {
        int locatorOffset = eocdOffset - 20;
        int sig = buffer.getInt(locatorOffset);
        if (sig == 0x07064b50) {
            int startDisk = buffer.getInt(locatorOffset + 4);
            long relOffset = buffer.getLong(locatorOffset + 8);
            int totalDisks = buffer.getInt(locatorOffset + 16);
            Map<String, Object> locator = new HashMap<>();
            locator.put("signature", Integer.toHexString(sig));
            locator.put("start_disk", startDisk);
            locator.put("relative_offset", relOffset);
            locator.put("total_disks", totalDisks);
            properties.put("zip64_locator", locator);

            int zip64Offset = (int) relOffset;
            sig = buffer.getInt(zip64Offset);
            if (sig == 0x06064b50) {
                long recordSize = buffer.getLong(zip64Offset + 4);
                short verMade = buffer.getShort(zip64Offset + 12);
                short verNeeded = buffer.getShort(zip64Offset + 14);
                int diskNum = buffer.getInt(zip64Offset + 16);
                int startDisk2 = buffer.getInt(zip64Offset + 20);
                long entriesDisk = buffer.getLong(zip64Offset + 24);
                long totalEntries = buffer.getLong(zip64Offset + 32);
                long cdSize = buffer.getLong(zip64Offset + 40);
                long cdOffset = buffer.getLong(zip64Offset + 48);
                Map<String, Object> zip64Eocd = new HashMap<>();
                zip64Eocd.put("signature", Integer.toHexString(sig));
                zip64Eocd.put("record_size", recordSize);
                zip64Eocd.put("version_made", Short.toUnsignedInt(verMade));
                zip64Eocd.put("version_needed", Short.toUnsignedInt(verNeeded));
                zip64Eocd.put("disk_number", diskNum);
                zip64Eocd.put("start_disk", startDisk2);
                zip64Eocd.put("entries_on_disk", entriesDisk);
                zip64Eocd.put("total_entries", totalEntries);
                zip64Eocd.put("cd_size", cdSize);
                zip64Eocd.put("cd_offset", cdOffset);
                properties.put("zip64_eocd", zip64Eocd);

                Map<String, Object> eocd = (Map<String, Object>) properties.get("eocd");
                eocd.put("total_entries", (int) totalEntries);
                eocd.put("cd_size", (int) cdSize);
                eocd.put("cd_offset", (int) cdOffset);
            }
        }
    }

    private Map<String, Object> parseLocalHeader(ByteBuffer buffer, int offset) {
        Map<String, Object> props = new HashMap<>();
        int sig = buffer.getInt(offset);
        short verNeeded = buffer.getShort(offset + 4);
        short flag = buffer.getShort(offset + 6);
        short compMethod = buffer.getShort(offset + 8);
        short modTime = buffer.getShort(offset + 10);
        short modDate = buffer.getShort(offset + 12);
        int crc = buffer.getInt(offset + 14);
        int compSize = buffer.getInt(offset + 18);
        int uncompSize = buffer.getInt(offset + 22);
        short fnLen = buffer.getShort(offset + 26);
        short extraLen = buffer.getShort(offset + 28);
        props.put("signature", Integer.toHexString(sig));
        props.put("version_needed", Short.toUnsignedInt(verNeeded));
        props.put("general_flag", Short.toUnsignedInt(flag));
        props.put("compression_method", Short.toUnsignedInt(compMethod));
        props.put("mod_time", Short.toUnsignedInt(modTime));
        props.put("mod_date", Short.toUnsignedInt(modDate));
        props.put("crc32", crc);
        props.put("compressed_size", compSize);
        props.put("uncompressed_size", uncompSize);
        props.put("file_name_length", Short.toUnsignedInt(fnLen));
        props.put("extra_length", Short.toUnsignedInt(extraLen));
        offset += 30;
        byte[] fnBytes = new byte[fnLen];
        buffer.position(offset);
        buffer.get(fnBytes);
        props.put("file_name", new String(fnBytes, (flag & 0x800) != 0 ? "UTF-8" : "Cp437"));
        offset += fnLen;
        byte[] extraBytes = new byte[extraLen];
        buffer.position(offset);
        buffer.get(extraBytes);
        props.put("extra_field", toHexString(extraBytes));
        props.put("header_size", 30 + fnLen + extraLen);
        // Parse ZIP64 extra
        if (compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF) {
            int extraOffset = 0;
            while (extraOffset < extraLen) {
                short extraId = buffer.getShort(offset + extraOffset);
                short extraSize = buffer.getShort(offset + extraOffset + 2);
                if (extraId == 0x0001) {
                    int dataOffset = extraOffset + 4;
                    if (uncompSize == 0xFFFFFFFF) {
                        props.put("uncompressed_size", buffer.getLong(offset + dataOffset));
                        dataOffset += 8;
                    }
                    if (compSize == 0xFFFFFFFF) {
                        props.put("compressed_size", buffer.getLong(offset + dataOffset));
                    }
                }
                extraOffset += 4 + extraSize;
            }
        }
        return props;
    }

    private Map<String, Object> parseCentralHeader(ByteBuffer buffer, int offset) {
        Map<String, Object> props = new HashMap<>();
        int sig = buffer.getInt(offset);
        short verMade = buffer.getShort(offset + 4);
        short verNeeded = buffer.getShort(offset + 6);
        short flag = buffer.getShort(offset + 8);
        short compMethod = buffer.getShort(offset + 10);
        short modTime = buffer.getShort(offset + 12);
        short modDate = buffer.getShort(offset + 14);
        int crc = buffer.getInt(offset + 16);
        int compSize = buffer.getInt(offset + 20);
        int uncompSize = buffer.getInt(offset + 24);
        short fnLen = buffer.getShort(offset + 28);
        short extraLen = buffer.getShort(offset + 30);
        short commentLen = buffer.getShort(offset + 32);
        short diskStart = buffer.getShort(offset + 34);
        short intAttr = buffer.getShort(offset + 36);
        int extAttr = buffer.getInt(offset + 38);
        int relOffset = buffer.getInt(offset + 42);
        props.put("signature", Integer.toHexString(sig));
        props.put("version_made", Short.toUnsignedInt(verMade));
        props.put("version_needed", Short.toUnsignedInt(verNeeded));
        props.put("general_flag", Short.toUnsignedInt(flag));
        props.put("compression_method", Short.toUnsignedInt(compMethod));
        props.put("mod_time", Short.toUnsignedInt(modTime));
        props.put("mod_date", Short.toUnsignedInt(modDate));
        props.put("crc32", crc);
        props.put("compressed_size", compSize);
        props.put("uncompressed_size", uncompSize);
        props.put("file_name_length", Short.toUnsignedInt(fnLen));
        props.put("extra_length", Short.toUnsignedInt(extraLen));
        props.put("comment_length", Short.toUnsignedInt(commentLen));
        props.put("disk_start", Short.toUnsignedInt(diskStart));
        props.put("internal_attributes", Short.toUnsignedInt(intAttr));
        props.put("external_attributes", extAttr);
        props.put("relative_offset", relOffset);
        offset += 46;
        byte[] fnBytes = new byte[fnLen];
        buffer.position(offset);
        buffer.get(fnBytes);
        props.put("file_name", new String(fnBytes, (flag & 0x800) != 0 ? "UTF-8" : "Cp437"));
        offset += fnLen;
        byte[] extraBytes = new byte[extraLen];
        buffer.position(offset);
        buffer.get(extraBytes);
        props.put("extra_field", toHexString(extraBytes));
        offset += extraLen;
        byte[] commentBytes = new byte[commentLen];
        buffer.position(offset);
        buffer.get(commentBytes);
        props.put("file_comment", new String(commentBytes, (flag & 0x800) != 0 ? "UTF-8" : "Cp437"));
        props.put("header_size", 46 + fnLen + extraLen + commentLen);
        // Parse ZIP64 extra
        if (compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF || relOffset == 0xFFFFFFFF) {
            int extraOffset = 0;
            int extraBase = offset - extraLen - commentLen;
            while (extraOffset < extraLen) {
                short extraId = buffer.getShort(extraBase + extraOffset);
                short extraSize = buffer.getShort(extraBase + extraOffset + 2);
                if (extraId == 0x0001) {
                    int dataOffset = extraOffset + 4;
                    if (uncompSize == 0xFFFFFFFF) {
                        props.put("uncompressed_size", buffer.getLong(extraBase + dataOffset));
                        dataOffset += 8;
                    }
                    if (compSize == 0xFFFFFFFF) {
                        props.put("compressed_size", buffer.getLong(extraBase + dataOffset));
                        dataOffset += 8;
                    }
                    if (relOffset == 0xFFFFFFFF) {
                        props.put("relative_offset", buffer.getLong(extraBase + dataOffset));
                    }
                }
                extraOffset += 4 + extraSize;
            }
        }
        return props;
    }

    private String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) sb.append(String.format("%02x", b));
        return sb.toString();
    }

    public void printProperties() {
        System.out.println("Local Files:");
        List<Map<String, Object>> locals = (List<Map<String, Object>>) properties.get("local_files");
        for (int i = 0; i < locals.size(); i++) {
            System.out.println("File " + (i + 1) + ":");
            locals.get(i).forEach((k, v) -> System.out.println("  " + k + ": " + v));
        }
        System.out.println("\nCentral Directories:");
        List<Map<String, Object>> cds = (List<Map<String, Object>>) properties.get("central_directories");
        for (int i = 0; i < cds.size(); i++) {
            System.out.println("Central Directory " + (i + 1) + ":");
            cds.get(i).forEach((k, v) -> System.out.println("  " + k + ": " + v));
        }
        if (properties.get("zip64_eocd") != null) {
            System.out.println("\nZip64 EOCD:");
            ((Map<String, Object>) properties.get("zip64_eocd")).forEach((k, v) -> System.out.println("  " + k + ": " + v));
        }
        if (properties.get("zip64_locator") != null) {
            System.out.println("\nZip64 Locator:");
            ((Map<String, Object>) properties.get("zip64_locator")).forEach((k, v) -> System.out.println("  " + k + ": " + v));
        }
        System.out.println("\nEOCD:");
        ((Map<String, Object>) properties.get("eocd")).forEach((k, v) -> System.out.println("  " + k + ": " + v));
    }

    public void write(String outputPath) throws IOException {
        // Basic write: Create a new ZIP with one file
        String addFileName = "sample.txt";
        byte[] addFileContent = "Hello, ZIP!".getBytes("UTF-8");
        CRC32 crc = new CRC32();
        crc.update(addFileContent);
        int fnLen = addFileName.length();
        ByteBuffer localHeader = ByteBuffer.allocate(30 + fnLen).order(ByteOrder.LITTLE_ENDIAN);
        localHeader.putInt(0x04034b50);
        localHeader.putShort((short) 10); // ver
        localHeader.putShort((short) 0); // flag
        localHeader.putShort((short) 0); // method
        localHeader.putShort((short) 0); // time
        localHeader.putShort((short) 0); // date
        localHeader.putInt((int) crc.getValue());
        localHeader.putInt(addFileContent.length);
        localHeader.putInt(addFileContent.length);
        localHeader.putShort((short) fnLen);
        localHeader.putShort((short) 0); // extra
        localHeader.put(addFileName.getBytes("UTF-8"));

        ByteBuffer cdHeader = ByteBuffer.allocate(46 + fnLen).order(ByteOrder.LITTLE_ENDIAN);
        cdHeader.putInt(0x02014b50);
        cdHeader.putShort((short) 10); // ver made
        cdHeader.putShort((short) 10); // ver needed
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putInt((int) crc.getValue());
        cdHeader.putInt(addFileContent.length);
        cdHeader.putInt(addFileContent.length);
        cdHeader.putShort((short) fnLen);
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putShort((short) 0);
        cdHeader.putInt(0);
        cdHeader.putInt(0); // rel offset 0
        cdHeader.put(addFileName.getBytes("UTF-8"));

        ByteBuffer eocd = ByteBuffer.allocate(22).order(ByteOrder.LITTLE_ENDIAN);
        eocd.putInt(0x06054b50);
        eocd.putShort((short) 0);
        eocd.putShort((short) 0);
        eocd.putShort((short) 1);
        eocd.putShort((short) 1);
        eocd.putInt(cdHeader.capacity());
        eocd.putInt(localHeader.capacity() + addFileContent.length);
        eocd.putShort((short) 0);

        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(localHeader.array());
            fos.write(addFileContent);
            fos.write(cdHeader.array());
            fos.write(eocd.array());
        }
    }

    public static void main(String[] args) {
        ZipHandler handler = new ZipHandler("sample.zip");
        handler.printProperties();
        try {
            handler.write("modified.zip");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This handles basic cases; for full ZIP64 in write, additional logic is needed.

6. JavaScript Class for .ZIP Handling

The following is the upgraded JavaScript class, with complete parsing logic (using DataView for unpacking). The write method returns a new Blob for a simple .ZIP file.

class ZipHandler {
    constructor(buffer) {
        this.buffer = buffer;
        this.properties = {};
        this.parse();
    }

    parse() {
        const view = new DataView(this.buffer);
        this.properties.local_files = [];
        this.properties.central_directories = [];
        this.properties.zip64_eocd = null;
        this.properties.zip64_locator = null;
        this.properties.eocd = {};

        // Find EOCD
        const eocdOffset = this.findEOCD(view);
        if (eocdOffset === -1) throw new Error('Invalid ZIP: No EOCD');

        // Parse EOCD
        this.parseEOCD(view, eocdOffset);

        // Parse Zip64 if present
        if (this.properties.eocd.zip64_present) {
            this.parseZip64(view, eocdOffset);
        }

        // Parse Central Directory
        let cdOffset = this.properties.eocd.cd_offset;
        for (let i = 0; i < this.properties.eocd.total_entries; i++) {
            const cdProps = this.parseCentralHeader(view, cdOffset);
            this.properties.central_directories.push(cdProps);
            cdOffset += cdProps.header_size;
        }

        // Parse Local Headers
        let offset = 0;
        for (let i = 0; i < this.properties.eocd.total_entries; i++) {
            const localProps = this.parseLocalHeader(view, offset);
            this.properties.local_files.push(localProps);
            offset += localProps.header_size + localProps.compressed_size;
            if (localProps.general_flag & 0x08) {
                offset += 12;
            }
        }
    }

    findEOCD(view) {
        for (let i = view.byteLength - 22; i >= 0; i--) {
            if (view.getUint32(i, true) === 0x06054b50) return i;
        }
        return -1;
    }

    parseEOCD(view, offset) {
        const sig = view.getUint32(offset, true);
        const diskNum = view.getUint16(offset + 4, true);
        const startDisk = view.getUint16(offset + 6, true);
        const entriesDisk = view.getUint16(offset + 8, true);
        const totalEntries = view.getUint16(offset + 10, true);
        const cdSize = view.getUint32(offset + 12, true);
        const cdOffset = view.getUint32(offset + 16, true);
        const commentLen = view.getUint16(offset + 20, true);
        const decoder = new TextDecoder();
        const comment = decoder.decode(new Uint8Array(this.buffer, offset + 22, commentLen));
        this.properties.eocd = {
            signature: `0x${sig.toString(16)}`,
            disk_number: diskNum,
            start_disk: startDisk,
            entries_on_disk: entriesDisk,
            total_entries: totalEntries,
            cd_size: cdSize,
            cd_offset: cdOffset,
            comment_length: commentLen,
            comment: comment,
            zip64_present: totalEntries === 0xFFFF || cdSize === 0xFFFFFFFF || cdOffset === 0xFFFFFFFF
        };
    }

    parseZip64(view, eocdOffset) {
        const locatorOffset = eocdOffset - 20;
        const sig = view.getUint32(locatorOffset, true);
        if (sig === 0x07064b50) {
            const startDisk = view.getUint32(locatorOffset + 4, true);
            const relOffset = view.getBigUint64(locatorOffset + 8, true);
            const totalDisks = view.getUint32(locatorOffset + 16, true);
            this.properties.zip64_locator = {
                signature: `0x${sig.toString(16)}`,
                start_disk: startDisk,
                relative_offset: Number(relOffset),
                total_disks: totalDisks
            };
            const zip64Offset = Number(relOffset);
            const zip64Sig = view.getUint32(zip64Offset, true);
            if (zip64Sig === 0x06064b50) {
                const recordSize = view.getBigUint64(zip64Offset + 4, true);
                const verMade = view.getUint16(zip64Offset + 12, true);
                const verNeeded = view.getUint16(zip64Offset + 14, true);
                const diskNum = view.getUint32(zip64Offset + 16, true);
                const startDisk2 = view.getUint32(zip64Offset + 20, true);
                const entriesDisk = view.getBigUint64(zip64Offset + 24, true);
                const totalEntries = view.getBigUint64(zip64Offset + 32, true);
                const cdSize = view.getBigUint64(zip64Offset + 40, true);
                const cdOffset = view.getBigUint64(zip64Offset + 48, true);
                this.properties.zip64_eocd = {
                    signature: `0x${zip64Sig.toString(16)}`,
                    record_size: Number(recordSize),
                    version_made: verMade,
                    version_needed: verNeeded,
                    disk_number: diskNum,
                    start_disk: startDisk2,
                    entries_on_disk: Number(entriesDisk),
                    total_entries: Number(totalEntries),
                    cd_size: Number(cdSize),
                    cd_offset: Number(cdOffset)
                };
                this.properties.eocd.total_entries = Number(totalEntries);
                this.properties.eocd.cd_size = Number(cdSize);
                this.properties.eocd.cd_offset = Number(cdOffset);
            }
        }
    }

    parseLocalHeader(view, offset) {
        const props = {};
        const sig = view.getUint32(offset, true);
        props.signature = `0x${sig.toString(16)}`;
        props.version_needed = view.getUint16(offset + 4, true);
        props.general_flag = view.getUint16(offset + 6, true);
        props.compression_method = view.getUint16(offset + 8, true);
        props.mod_time = view.getUint16(offset + 10, true);
        props.mod_date = view.getUint16(offset + 12, true);
        props.crc32 = view.getUint32(offset + 14, true);
        let compSize = view.getUint32(offset + 18, true);
        let uncompSize = view.getUint32(offset + 22, true);
        const fnLen = view.getUint16(offset + 26, true);
        const extraLen = view.getUint16(offset + 28, true);
        props.file_name_length = fnLen;
        props.extra_length = extraLen;
        offset += 30;
        const decoder = new TextDecoder(props.general_flag & 0x800 ? 'utf-8' : 'iso-8859-1');
        props.file_name = decoder.decode(new Uint8Array(this.buffer, offset, fnLen));
        offset += fnLen;
        props.extra_field = Array.from(new Uint8Array(this.buffer, offset, extraLen)).map(b => b.toString(16).padStart(2, '0')).join('');
        props.header_size = 30 + fnLen + extraLen;
        props.compressed_size = compSize;
        props.uncompressed_size = uncompSize;
        // Parse ZIP64 extra
        if (compSize === 0xFFFFFFFF || uncompSize === 0xFFFFFFFF) {
            let extraOffset = 0;
            while (extraOffset < extraLen) {
                const extraId = view.getUint16(offset + extraOffset, true);
                const extraSize = view.getUint16(offset + extraOffset + 2, true);
                if (extraId === 0x0001) {
                    let dataOffset = extraOffset + 4;
                    if (uncompSize === 0xFFFFFFFF) {
                        props.uncompressed_size = Number(view.getBigUint64(offset + dataOffset, true));
                        dataOffset += 8;
                    }
                    if (compSize === 0xFFFFFFFF) {
                        props.compressed_size = Number(view.getBigUint64(offset + dataOffset, true));
                    }
                }
                extraOffset += 4 + extraSize;
            }
        }
        return props;
    }

    parseCentralHeader(view, offset) {
        const props = {};
        const sig = view.getUint32(offset, true);
        props.signature = `0x${sig.toString(16)}`;
        props.version_made = view.getUint16(offset + 4, true);
        props.version_needed = view.getUint16(offset + 6, true);
        props.general_flag = view.getUint16(offset + 8, true);
        props.compression_method = view.getUint16(offset + 10, true);
        props.mod_time = view.getUint16(offset + 12, true);
        props.mod_date = view.getUint16(offset + 14, true);
        props.crc32 = view.getUint32(offset + 16, true);
        let compSize = view.getUint32(offset + 20, true);
        let uncompSize = view.getUint32(offset + 24, true);
        const fnLen = view.getUint16(offset + 28, true);
        const extraLen = view.getUint16(offset + 30, true);
        const commentLen = view.getUint16(offset + 32, true);
        props.disk_start = view.getUint16(offset + 34, true);
        props.internal_attributes = view.getUint16(offset + 36, true);
        props.external_attributes = view.getUint32(offset + 38, true);
        let relOffset = view.getUint32(offset + 42, true);
        props.file_name_length = fnLen;
        props.extra_length = extraLen;
        props.comment_length = commentLen;
        offset += 46;
        const decoder = new TextDecoder(props.general_flag & 0x800 ? 'utf-8' : 'iso-8859-1');
        props.file_name = decoder.decode(new Uint8Array(this.buffer, offset, fnLen));
        offset += fnLen;
        props.extra_field = Array.from(new Uint8Array(this.buffer, offset, extraLen)).map(b => b.toString(16).padStart(2, '0')).join('');
        offset += extraLen;
        props.file_comment = decoder.decode(new Uint8Array(this.buffer, offset, commentLen));
        props.header_size = 46 + fnLen + extraLen + commentLen;
        props.compressed_size = compSize;
        props.uncompressed_size = uncompSize;
        props.relative_offset = relOffset;
        // Parse ZIP64 extra
        if (compSize === 0xFFFFFFFF || uncompSize === 0xFFFFFFFF || relOffset === 0xFFFFFFFF) {
            let extraOffset = 0;
            const extraBase = offset - extraLen - commentLen;
            while (extraOffset < extraLen) {
                const extraId = view.getUint16(extraBase + extraOffset, true);
                const extraSize = view.getUint16(extraBase + extraOffset + 2, true);
                if (extraId === 0x0001) {
                    let dataOffset = extraOffset + 4;
                    if (uncompSize === 0xFFFFFFFF) {
                        props.uncompressed_size = Number(view.getBigUint64(extraBase + dataOffset, true));
                        dataOffset += 8;
                    }
                    if (compSize === 0xFFFFFFFF) {
                        props.compressed_size = Number(view.getBigUint64(extraBase + dataOffset, true));
                        dataOffset += 8;
                    }
                    if (relOffset === 0xFFFFFFFF) {
                        props.relative_offset = Number(view.getBigUint64(extraBase + dataOffset, true));
                    }
                }
                extraOffset += 4 + extraSize;
            }
        }
        return props;
    }

    printProperties() {
        console.log('Local Files:');
        this.properties.local_files.forEach((props, i) => {
            console.log(`File ${i + 1}:`);
            Object.entries(props).forEach(([key, value]) => console.log(`  ${key}: ${value}`));
        });
        console.log('\nCentral Directories:');
        this.properties.central_directories.forEach((props, i) => {
            console.log(`Central Directory ${i + 1}:`);
            Object.entries(props).forEach(([key, value]) => console.log(`  ${key}: ${value}`));
        });
        if (this.properties.zip64_eocd) {
            console.log('\nZip64 EOCD:');
            Object.entries(this.properties.zip64_eocd).forEach(([key, value]) => console.log(`  ${key}: ${value}`));
        }
        if (this.properties.zip64_locator) {
            console.log('\nZip64 Locator:');
            Object.entries(this.properties.zip64_locator).forEach(([key, value]) => console.log(`  ${key}: ${value}`));
        }
        console.log('\nEOCD:');
        Object.entries(this.properties.eocd).forEach(([key, value]) => console.log(`  ${key}: ${value}`));
    }

    write() {
        // Basic write: Create a new ZIP Blob with one file
        const addFileName = 'sample.txt';
        const addFileContent = new TextEncoder().encode('Hello, ZIP!');
        const crc = this.calculateCRC32(addFileContent);
        const fnLen = addFileName.length;
        const localHeader = new ArrayBuffer(30 + fnLen);
        const localView = new DataView(localHeader);
        localView.setUint32(0, 0x04034b50, true);
        localView.setUint16(4, 10, true);
        localView.setUint16(6, 0, true);
        localView.setUint16(8, 0, true);
        localView.setUint16(10, 0, true);
        localView.setUint16(12, 0, true);
        localView.setUint32(14, crc, true);
        localView.setUint32(18, addFileContent.length, true);
        localView.setUint32(22, addFileContent.length, true);
        localView.setUint16(26, fnLen, true);
        localView.setUint16(28, 0, true);
        const nameBytes = new TextEncoder().encode(addFileName);
        new Uint8Array(localHeader, 30).set(nameBytes);

        const cdHeader = new ArrayBuffer(46 + fnLen);
        const cdView = new DataView(cdHeader);
        cdView.setUint32(0, 0x02014b50, true);
        cdView.setUint16(4, 10, true);
        cdView.setUint16(6, 10, true);
        cdView.setUint16(8, 0, true);
        cdView.setUint16(10, 0, true);
        cdView.setUint16(12, 0, true);
        cdView.setUint16(14, 0, true);
        cdView.setUint32(16, crc, true);
        cdView.setUint32(20, addFileContent.length, true);
        cdView.setUint32(24, addFileContent.length, true);
        cdView.setUint16(28, fnLen, true);
        cdView.setUint16(30, 0, true);
        cdView.setUint16(32, 0, true);
        cdView.setUint16(34, 0, true);
        cdView.setUint16(36, 0, true);
        cdView.setUint32(38, 0, true);
        cdView.setUint32(42, 0, true);
        new Uint8Array(cdHeader, 46).set(nameBytes);

        const eocd = new ArrayBuffer(22);
        const eocdView = new DataView(eocd);
        eocdView.setUint32(0, 0x06054b50, true);
        eocdView.setUint16(4, 0, true);
        eocdView.setUint16(6, 0, true);
        eocdView.setUint16(8, 1, true);
        eocdView.setUint16(10, 1, true);
        eocdView.setUint32(12, cdHeader.byteLength, true);
        eocdView.setUint32(16, localHeader.byteLength + addFileContent.length, true);
        eocdView.setUint16(20, 0, true);

        return new Blob([localHeader, addFileContent, cdHeader, eocd], { type: 'application/zip' });
    }

    calculateCRC32(bytes) {
        let crc = 0xFFFFFFFF;
        for (let byte of bytes) {
            crc ^= byte;
            for (let i = 0; i < 8; i++) {
                crc = (crc & 1) ? (crc >>> 1) ^ 0xEDB88320 : crc >>> 1;
            }
        }
        return crc ^ 0xFFFFFFFF;
    }
}

// Usage example (in browser or Node with buffer)
// const reader = new FileReader();
// reader.onload = () => {
//     const handler = new ZipHandler(reader.result);
//     handler.printProperties();
//     const newZip = handler.write();
//     // Save newZip as file
// };
// reader.readAsArrayBuffer(file);

This is suitable for browser or Node.js environments.

7. C++ Class for .ZIP Handling

The following is the upgraded C++ class, with complete parsing logic using raw byte access and little-endian unpacking. The write method creates a new .ZIP file with one sample file. Compile with -std=c++11 or later.

#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <iomanip>
#include <cstdint>
#include <cstring>

class ZipHandler {
private:
    std::string filepath;
    std::map<std::string, std::vector<std::map<std::string, std::string>>> file_properties; // local_files, central_directories
    std::map<std::string, std::string> zip64_eocd;
    std::map<std::string, std::string> zip64_locator;
    std::map<std::string, std::string> eocd;

public:
    ZipHandler(const std::string& fp) : filepath(fp) {
        parse();
    }

    void parse() {
        std::ifstream file(filepath, std::ios::binary | std::ios::ate);
        if (!file) return;
        size_t size = file.tellg();
        file.seekg(0);
        std::vector<uint8_t> data(size);
        file.read(reinterpret_cast<char*>(data.data()), size);

        file_properties["local_files"] = {};
        file_properties["central_directories"] = {};

        // Find EOCD
        ssize_t eocd_offset = find_eocd(data);
        if (eocd_offset == -1) {
            std::cerr << "Invalid ZIP: No EOCD" << std::endl;
            return;
        }

        // Parse EOCD
        parse_eocd(data, eocd_offset);

        // Parse Zip64 if present
        bool zip64_present = (stoi(eocd["total_entries"]) == 0xFFFF ||
                              stoi(eocd["cd_size"]) == 0xFFFFFFFF ||
                              stoi(eocd["cd_offset"]) == 0xFFFFFFFF);
        if (zip64_present) {
            parse_zip64(data, eocd_offset);
        }

        // Parse Central Directory
        uint64_t cd_offset = stoull(eocd["cd_offset"]);
        uint64_t total_entries = stoull(eocd["total_entries"]);
        for (uint64_t i = 0; i < total_entries; ++i) {
            auto cd_props = parse_central_header(data, cd_offset);
            file_properties["central_directories"].push_back(cd_props);
            cd_offset += stoull(cd_props.at("header_size"));
        }

        // Parse Local Headers
        size_t offset = 0;
        for (uint64_t i = 0; i < total_entries; ++i) {
            auto local_props = parse_local_header(data, offset);
            file_properties["local_files"].push_back(local_props);
            offset += stoull(local_props.at("header_size")) + stoull(local_props.at("compressed_size"));
            if (stoi(local_props.at("general_flag")) & 0x08) {
                offset += 12;
            }
        }
    }

    ssize_t find_eocd(const std::vector<uint8_t>& data) {
        for (ssize_t i = data.size() - 22; i >= 0; --i) {
            uint32_t sig;
            memcpy(&sig, &data[i], 4);
            if (sig == 0x06054b50) return i;
        }
        return -1;
    }

    void parse_eocd(const std::vector<uint8_t>& data, size_t offset) {
        uint32_t sig;
        uint16_t disk_num, start_disk, entries_disk, total_entries, comment_len;
        uint32_t cd_size, cd_offset;
        memcpy(&sig, &data[offset], 4);
        memcpy(&disk_num, &data[offset + 4], 2);
        memcpy(&start_disk, &data[offset + 6], 2);
        memcpy(&entries_disk, &data[offset + 8], 2);
        memcpy(&total_entries, &data[offset + 10], 2);
        memcpy(&cd_size, &data[offset + 12], 4);
        memcpy(&cd_offset, &data[offset + 16], 4);
        memcpy(&comment_len, &data[offset + 20], 2);
        std::string comment(reinterpret_cast<const char*>(&data[offset + 22]), comment_len);
        eocd["signature"] = "0x" + to_hex(sig);
        eocd["disk_number"] = std::to_string(disk_num);
        eocd["start_disk"] = std::to_string(start_disk);
        eocd["entries_on_disk"] = std::to_string(entries_disk);
        eocd["total_entries"] = std::to_string(total_entries);
        eocd["cd_size"] = std::to_string(cd_size);
        eocd["cd_offset"] = std::to_string(cd_offset);
        eocd["comment_length"] = std::to_string(comment_len);
        eocd["comment"] = comment;
    }

    void parse_zip64(const std::vector<uint8_t>& data, size_t eocd_offset) {
        size_t locator_offset = eocd_offset - 20;
        uint32_t sig;
        memcpy(&sig, &data[locator_offset], 4);
        if (sig == 0x07064b50) {
            uint32_t start_disk, total_disks;
            uint64_t rel_offset;
            memcpy(&start_disk, &data[locator_offset + 4], 4);
            memcpy(&rel_offset, &data[locator_offset + 8], 8);
            memcpy(&total_disks, &data[locator_offset + 16], 4);
            zip64_locator["signature"] = "0x" + to_hex(sig);
            zip64_locator["start_disk"] = std::to_string(start_disk);
            zip64_locator["relative_offset"] = std::to_string(rel_offset);
            zip64_locator["total_disks"] = std::to_string(total_disks);

            size_t zip64_offset = rel_offset;
            uint32_t zip64_sig;
            memcpy(&zip64_sig, &data[zip64_offset], 4);
            if (zip64_sig == 0x06064b50) {
                uint64_t record_size;
                uint16_t ver_made, ver_needed;
                uint32_t disk_num, start_disk2;
                uint64_t entries_disk, total_entries, cd_size, cd_offset;
                memcpy(&record_size, &data[zip64_offset + 4], 8);
                memcpy(&ver_made, &data[zip64_offset + 12], 2);
                memcpy(&ver_needed, &data[zip64_offset + 14], 2);
                memcpy(&disk_num, &data[zip64_offset + 16], 4);
                memcpy(&start_disk2, &data[zip64_offset + 20], 4);
                memcpy(&entries_disk, &data[zip64_offset + 24], 8);
                memcpy(&total_entries, &data[zip64_offset + 32], 8);
                memcpy(&cd_size, &data[zip64_offset + 40], 8);
                memcpy(&cd_offset, &data[zip64_offset + 48], 8);
                zip64_eocd["signature"] = "0x" + to_hex(zip64_sig);
                zip64_eocd["record_size"] = std::to_string(record_size);
                zip64_eocd["version_made"] = std::to_string(ver_made);
                zip64_eocd["version_needed"] = std::to_string(ver_needed);
                zip64_eocd["disk_number"] = std::to_string(disk_num);
                zip64_eocd["start_disk"] = std::to_string(start_disk2);
                zip64_eocd["entries_on_disk"] = std::to_string(entries_disk);
                zip64_eocd["total_entries"] = std::to_string(total_entries);
                zip64_eocd["cd_size"] = std::to_string(cd_size);
                zip64_eocd["cd_offset"] = std::to_string(cd_offset);

                eocd["total_entries"] = std::to_string(total_entries);
                eocd["cd_size"] = std::to_string(cd_size);
                eocd["cd_offset"] = std::to_string(cd_offset);
            }
        }
    }

    std::map<std::string, std::string> parse_local_header(const std::vector<uint8_t>& data, size_t offset) {
        std::map<std::string, std::string> props;
        uint32_t sig, crc, comp_size, uncomp_size;
        uint16_t ver_needed, flag, comp_method, mod_time, mod_date, fn_len, extra_len;
        memcpy(&sig, &data[offset], 4);
        memcpy(&ver_needed, &data[offset + 4], 2);
        memcpy(&flag, &data[offset + 6], 2);
        memcpy(&comp_method, &data[offset + 8], 2);
        memcpy(&mod_time, &data[offset + 10], 2);
        memcpy(&mod_date, &data[offset + 12], 2);
        memcpy(&crc, &data[offset + 14], 4);
        memcpy(&comp_size, &data[offset + 18], 4);
        memcpy(&uncomp_size, &data[offset + 22], 4);
        memcpy(&fn_len, &data[offset + 26], 2);
        memcpy(&extra_len, &data[offset + 28], 2);
        props["signature"] = "0x" + to_hex(sig);
        props["version_needed"] = std::to_string(ver_needed);
        props["general_flag"] = std::to_string(flag);
        props["compression_method"] = std::to_string(comp_method);
        props["mod_time"] = std::to_string(mod_time);
        props["mod_date"] = std::to_string(mod_date);
        props["crc32"] = std::to_string(crc);
        props["compressed_size"] = std::to_string(comp_size);
        props["uncompressed_size"] = std::to_string(uncomp_size);
        props["file_name_length"] = std::to_string(fn_len);
        props["extra_length"] = std::to_string(extra_len);
        offset += 30;
        std::string file_name(reinterpret_cast<const char*>(&data[offset]), fn_len);
        props["file_name"] = file_name;
        offset += fn_len;
        props["extra_field"] = to_hex_string(&data[offset], extra_len);
        props["header_size"] = std::to_string(30 + fn_len + extra_len);
        // Parse ZIP64 extra
        if (comp_size == 0xFFFFFFFF || uncomp_size == 0xFFFFFFFF) {
            size_t extra_offset = 0;
            while (extra_offset < extra_len) {
                uint16_t extra_id, extra_size;
                memcpy(&extra_id, &data[offset + extra_offset], 2);
                memcpy(&extra_size, &data[offset + extra_offset + 2], 2);
                if (extra_id == 0x0001) {
                    size_t data_offset = extra_offset + 4;
                    if (uncomp_size == 0xFFFFFFFF) {
                        uint64_t val;
                        memcpy(&val, &data[offset + data_offset], 8);
                        props["uncompressed_size"] = std::to_string(val);
                        data_offset += 8;
                    }
                    if (comp_size == 0xFFFFFFFF) {
                        uint64_t val;
                        memcpy(&val, &data[offset + data_offset], 8);
                        props["compressed_size"] = std::to_string(val);
                    }
                }
                extra_offset += 4 + extra_size;
            }
        }
        return props;
    }

    std::map<std::string, std::string> parse_central_header(const std::vector<uint8_t>& data, size_t offset) {
        std::map<std::string, std::string> props;
        uint32_t sig, crc, comp_size, uncomp_size, ext_attr, rel_offset;
        uint16_t ver_made, ver_needed, flag, comp_method, mod_time, mod_date, fn_len, extra_len, comment_len, disk_start, int_attr;
        memcpy(&sig, &data[offset], 4);
        memcpy(&ver_made, &data[offset + 4], 2);
        memcpy(&ver_needed, &data[offset + 6], 2);
        memcpy(&flag, &data[offset + 8], 2);
        memcpy(&comp_method, &data[offset + 10], 2);
        memcpy(&mod_time, &data[offset + 12], 2);
        memcpy(&mod_date, &data[offset + 14], 2);
        memcpy(&crc, &data[offset + 16], 4);
        memcpy(&comp_size, &data[offset + 20], 4);
        memcpy(&uncomp_size, &data[offset + 24], 4);
        memcpy(&fn_len, &data[offset + 28], 2);
        memcpy(&extra_len, &data[offset + 30], 2);
        memcpy(&comment_len, &data[offset + 32], 2);
        memcpy(&disk_start, &data[offset + 34], 2);
        memcpy(&int_attr, &data[offset + 36], 2);
        memcpy(&ext_attr, &data[offset + 38], 4);
        memcpy(&rel_offset, &data[offset + 42], 4);
        props["signature"] = "0x" + to_hex(sig);
        props["version_made"] = std::to_string(ver_made);
        props["version_needed"] = std::to_string(ver_needed);
        props["general_flag"] = std::to_string(flag);
        props["compression_method"] = std::to_string(comp_method);
        props["mod_time"] = std::to_string(mod_time);
        props["mod_date"] = std::to_string(mod_date);
        props["crc32"] = std::to_string(crc);
        props["compressed_size"] = std::to_string(comp_size);
        props["uncompressed_size"] = std::to_string(uncomp_size);
        props["file_name_length"] = std::to_string(fn_len);
        props["extra_length"] = std::to_string(extra_len);
        props["comment_length"] = std::to_string(comment_len);
        props["disk_start"] = std::to_string(disk_start);
        props["internal_attributes"] = std::to_string(int_attr);
        props["external_attributes"] = std::to_string(ext_attr);
        props["relative_offset"] = std::to_string(rel_offset);
        offset += 46;
        std::string file_name(reinterpret_cast<const char*>(&data[offset]), fn_len);
        props["file_name"] = file_name;
        offset += fn_len;
        props["extra_field"] = to_hex_string(&data[offset], extra_len);
        offset += extra_len;
        std::string file_comment(reinterpret_cast<const char*>(&data[offset]), comment_len);
        props["file_comment"] = file_comment;
        props["header_size"] = std::to_string(46 + fn_len + extra_len + comment_len);
        // Parse ZIP64 extra
        if (comp_size == 0xFFFFFFFF || uncomp_size == 0xFFFFFFFF || rel_offset == 0xFFFFFFFF) {
            size_t extra_offset = 0;
            size_t extra_base = offset - extra_len - comment_len;
            while (extra_offset < extra_len) {
                uint16_t extra_id, extra_size;
                memcpy(&extra_id, &data[extra_base + extra_offset], 2);
                memcpy(&extra_size, &data[extra_base + extra_offset + 2], 2);
                if (extra_id == 0x0001) {
                    size_t data_offset = extra_offset + 4;
                    if (uncomp_size == 0xFFFFFFFF) {
                        uint64_t val;
                        memcpy(&val, &data[extra_base + data_offset], 8);
                        props["uncompressed_size"] = std::to_string(val);
                        data_offset += 8;
                    }
                    if (comp_size == 0xFFFFFFFF) {
                        uint64_t val;
                        memcpy(&val, &data[extra_base + data_offset], 8);
                        props["compressed_size"] = std::to_string(val);
                        data_offset += 8;
                    }
                    if (rel_offset == 0xFFFFFFFF) {
                        uint64_t val;
                        memcpy(&val, &data[extra_base + data_offset], 8);
                        props["relative_offset"] = std::to_string(val);
                    }
                }
                extra_offset += 4 + extra_size;
            }
        }
        return props;
    }

    std::string to_hex(uint32_t val) {
        std::stringstream ss;
        ss << std::hex << val;
        return ss.str();
    }

    std::string to_hex_string(const uint8_t* bytes, size_t len) {
        std::stringstream ss;
        for (size_t i = 0; i < len; ++i) {
            ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]);
        }
        return ss.str();
    }

    void printProperties() {
        std::cout << "Local Files:" << std::endl;
        for (size_t i = 0; i < file_properties["local_files"].size(); ++i) {
            std::cout << "File " << (i + 1) << ":" << std::endl;
            for (const auto& kv : file_properties["local_files"][i]) {
                std::cout << "  " << kv.first << ": " << kv.second << std::endl;
            }
        }
        std::cout << "\nCentral Directories:" << std::endl;
        for (size_t i = 0; i < file_properties["central_directories"].size(); ++i) {
            std::cout << "Central Directory " << (i + 1) << ":" << std::endl;
            for (const auto& kv : file_properties["central_directories"][i]) {
                std::cout << "  " << kv.first << ": " << kv.second << std::endl;
            }
        }
        if (!zip64_eocd.empty()) {
            std::cout << "\nZip64 EOCD:" << std::endl;
            for (const auto& kv : zip64_eocd) {
                std::cout << "  " << kv.first << ": " << kv.second << std::endl;
            }
        }
        if (!zip64_locator.empty()) {
            std::cout << "\nZip64 Locator:" << std::endl;
            for (const auto& kv : zip64_locator) {
                std::cout << "  " << kv.first << ": " << kv.second << std::endl;
            }
        }
        std::cout << "\nEOCD:" << std::endl;
        for (const auto& kv : eocd) {
            std::cout << "  " << kv.first << ": " << kv.second << std::endl;
        }
    }

    void write(const std::string& output_path) {
        // Basic write: Create a new ZIP with one file
        std::string add_file_name = "sample.txt";
        std::string add_file_content = "Hello, ZIP!";
        uint32_t crc = calculate_crc32(reinterpret_cast<const uint8_t*>(add_file_content.data()), add_file_content.size());
        uint16_t fn_len = add_file_name.length();

        std::vector<uint8_t> local_header(30 + fn_len);
        uint32_t sig = 0x04034b50;
        memcpy(&local_header[0], &sig, 4);
        uint16_t ver = 10; memcpy(&local_header[4], &ver, 2);
        uint16_t flag = 0; memcpy(&local_header[6], &flag, 2);
        uint16_t method = 0; memcpy(&local_header[8], &method, 2);
        uint16_t time = 0; memcpy(&local_header[10], &time, 2);
        uint16_t date = 0; memcpy(&local_header[12], &date, 2);
        memcpy(&local_header[14], &crc, 4);
        uint32_t size = add_file_content.size(); memcpy(&local_header[18], &size, 4);
        memcpy(&local_header[22], &size, 4);
        memcpy(&local_header[26], &fn_len, 2);
        uint16_t extra = 0; memcpy(&local_header[28], &extra, 2);
        memcpy(&local_header[30], add_file_name.data(), fn_len);

        std::vector<uint8_t> cd_header(46 + fn_len);
        sig = 0x02014b50;
        memcpy(&cd_header[0], &sig, 4);
        memcpy(&cd_header[4], &ver, 2);
        memcpy(&cd_header[6], &ver, 2);
        memcpy(&cd_header[8], &flag, 2);
        memcpy(&cd_header[10], &method, 2);
        memcpy(&cd_header[12], &time, 2);
        memcpy(&cd_header[14], &date, 2);
        memcpy(&cd_header[16], &crc, 4);
        memcpy(&cd_header[20], &size, 4);
        memcpy(&cd_header[24], &size, 4);
        memcpy(&cd_header[28], &fn_len, 2);
        memcpy(&cd_header[30], &extra, 2);
        uint16_t zero16 = 0; memcpy(&cd_header[32], &zero16, 2);
        memcpy(&cd_header[34], &zero16, 2);
        memcpy(&cd_header[36], &zero16, 2);
        uint32_t zero32 = 0; memcpy(&cd_header[38], &zero32, 4);
        memcpy(&cd_header[42], &zero32, 4);
        memcpy(&cd_header[46], add_file_name.data(), fn_len);

        std::vector<uint8_t> eocd_data(22);
        sig = 0x06054b50;
        memcpy(&eocd_data[0], &sig, 4);
        memcpy(&eocd_data[4], &zero16, 2);
        memcpy(&eocd_data[6], &zero16, 2);
        uint16_t one = 1; memcpy(&eocd_data[8], &one, 2);
        memcpy(&eocd_data[10], &one, 2);
        uint32_t cd_size = cd_header.size(); memcpy(&eocd_data[12], &cd_size, 4);
        uint32_t local_end = local_header.size() + add_file_content.size(); memcpy(&eocd_data[16], &local_end, 4);
        memcpy(&eocd_data[20], &zero16, 2);

        std::ofstream out(output_path, std::ios::binary);
        out.write(reinterpret_cast<const char*>(local_header.data()), local_header.size());
        out.write(add_file_content.data(), add_file_content.size());
        out.write(reinterpret_cast<const char*>(cd_header.data()), cd_header.size());
        out.write(reinterpret_cast<const char*>(eocd_data.data()), eocd_data.size());
    }

    uint32_t calculate_crc32(const uint8_t* data, size_t len) {
        uint32_t crc = 0xFFFFFFFF;
        for (size_t i = 0; i < len; ++i) {
            crc ^= data[i];
            for (int j = 0; j < 8; ++j) {
                crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : crc >> 1;
            }
        }
        return crc ^ 0xFFFFFFFF;
    }
};

int main() {
    ZipHandler handler("sample.zip");
    handler.printProperties();
    handler.write("modified.zip");
    return 0;
}

This implementation focuses on core functionality; for big-endian systems, additional endian swaps may be required.