Task 016: .ADZ File Format

Task 016: .ADZ File Forma

The .ADZ file format is a compressed Amiga Disk File (ADF) that uses GZIP compression to store disk images, primarily for Amiga computers and emulators. Below, I address each part of your task based on the available information and clarify the approach for handling this file format.


1. Properties of the .ADZ File Format Intrinsic to Its File System

The .ADZ file format is essentially a GZIP-compressed version of an Amiga Disk File (ADF), which is a disk image format used by Amiga systems. The intrinsic properties of the .ADZ file format are derived from its structure as a GZIP archive containing an ADF file and the characteristics of the ADF format itself. Based on the available information, the key properties include:

  • File Extension: .adz
  • Compression Type: GZIP (lossless compression)
  • Underlying Format: Amiga Disk File (ADF), which is a sector-by-sector copy of an Amiga floppy disk
  • File Category: Disk Image File, also classified as a Compressed File and Emulator File
  • Purpose: Stores an exact copy of an Amiga disk (e.g., for games or applications) in a compressed form to reduce storage size
  • Size Efficiency: Smaller than uncompressed ADF files due to GZIP compression, making it easier to store and transfer
  • Compatibility: Supported by Amiga emulators (e.g., WinUAE, Amiga Forever, E-UAE) and some archive tools (e.g., 7-Zip, WinZip, The Unarchiver)
  • MIME Type: Not explicitly defined in standards, often treated as application/x-gzip due to GZIP compression
  • Structure:
  • GZIP header (standard GZIP format, including magic number 1f 8b, compression method, flags, etc.)
  • Compressed ADF data (raw disk image of an Amiga floppy, typically 880 KB for a standard 3.5" DD disk)
  • GZIP footer (contains CRC-32 checksum and uncompressed size)
  • Platform Support: Cross-platform (Windows, macOS, Linux) with appropriate software
  • Integrity: GZIP compression ensures data integrity via CRC-32 checksums
  • Non-standard Variants: Some sources suggest ADZ files may occasionally use ZIP compression instead of GZIP, though this is less common and typically associated with Italian ZIP archives

Notes:

  • The ADF format within an ADZ file typically represents a 3.5" double-density Amiga floppy disk (880 KB, 11 sectors per track, 80 tracks per side, 2 sides).
  • No official public specification exists for .ADZ beyond its role as a GZIP-compressed ADF. The format is defined by the behavior of tools like WinUAE and the GZIP standard (RFC 1952).
  • The properties listed focus on the file system and format characteristics, excluding application-specific metadata (e.g., emulator configurations).

2. Python Class for .ADZ File Handling

This Python class opens an .ADZ file, decompresses it to extract the ADF content, reads and writes .ADZ files, and prints the identified properties. Since the ADF format is a raw disk image, we focus on extracting and displaying the properties listed above.

import gzip
import os
import struct

class ADZFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            "file_extension": ".adz",
            "compression_type": "GZIP",
            "underlying_format": "ADF",
            "file_category": "Disk Image, Compressed, Emulator",
            "purpose": "Store compressed Amiga disk image",
            "size_efficiency": "Compressed to reduce storage size",
            "compatibility": "WinUAE, Amiga Forever, E-UAE, 7-Zip, WinZip, The Unarchiver",
            "mime_type": "application/x-gzip",
            "platform_support": "Windows, macOS, Linux",
            "integrity": "CRC-32 checksum via GZIP"
        }
        self.adf_data = None
        self.file_size = None
        self.uncompressed_size = None
        self.crc32 = None

    def read_adz(self):
        """Read and decompress the .ADZ file."""
        try:
            with gzip.open(self.filepath, 'rb') as f:
                self.adf_data = f.read()
                self.file_size = os.path.getsize(self.filepath)
                # Extract GZIP footer (last 8 bytes of file for CRC-32 and uncompressed size)
                with open(self.filepath, 'rb') as raw_f:
                    raw_f.seek(-8, os.SEEK_END)
                    footer = raw_f.read(8)
                    self.crc32, self.uncompressed_size = struct.unpack('<II', footer)
            return self.adf_data
        except gzip.BadGzipFile:
            raise ValueError("Invalid .ADZ file: Not a valid GZIP-compressed file")
        except Exception as e:
            raise Exception(f"Error reading .ADZ file: {str(e)}")

    def write_adz(self, output_filepath, adf_data=None):
        """Write ADF data to a new .ADZ file."""
        try:
            with gzip.open(output_filepath, 'wb') as f:
                data = adf_data if adf_data is not None else self.adf_data
                if data is None:
                    raise ValueError("No ADF data to write")
                f.write(data)
            self.filepath = output_filepath
            self.read_adz()  # Update properties after writing
        except Exception as e:
            raise Exception(f"Error writing .ADZ file: {str(e)}")

    def print_properties(self):
        """Print all properties of the .ADZ file."""
        print("ADZ File Properties:")
        for key, value in self.properties.items():
            print(f"  {key}: {value}")
        print(f"  file_size: {self.file_size} bytes" if self.file_size else "  file_size: Not loaded")
        print(f"  uncompressed_size: {self.uncompressed_size} bytes" if self.uncompressed_size else "  uncompressed_size: Not loaded")
        print(f"  crc32: {hex(self.crc32) if self.crc32 else 'Not loaded'}")

    def get_adf_data(self):
        """Return the decompressed ADF data."""
        return self.adf_data

# Example usage
if __name__ == "__main__":
    try:
        adz = ADZFile("example.adz")
        adz.read_adz()
        adz.print_properties()
        # Write to a new .ADZ file
        adz.write_adz("output.adz")
    except Exception as e:
        print(f"Error: {str(e)}")

Explanation:

  • Reading: Uses Python's gzip module to decompress the .ADZ file and extract the ADF data. Reads the GZIP footer to obtain the CRC-32 and uncompressed size.
  • Writing: Compresses ADF data into a new .ADZ file using GZIP compression.
  • Properties: Prints all intrinsic properties, including file size, uncompressed size, and CRC-32 checksum.
  • Error Handling: Handles invalid GZIP files and other I/O errors.
  • Limitations: Assumes the .ADZ file contains a valid ADF image; does not parse ADF structure (e.g., Amiga filesystem details) as it’s a raw disk image.

3. Java Class for .ADZ File Handling

This Java class performs similar operations using Java's GZIP handling capabilities.

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class ADZFile {
    private String filepath;
    private Map<String, String> properties;
    private byte[] adfData;
    private long fileSize;
    private long uncompressedSize;
    private int crc32;

    public ADZFile(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        this.properties.put("file_extension", ".adz");
        this.properties.put("compression_type", "GZIP");
        this.properties.put("underlying_format", "ADF");
        this.properties.put("file_category", "Disk Image, Compressed, Emulator");
        this.properties.put("purpose", "Store compressed Amiga disk image");
        this.properties.put("size_efficiency", "Compressed to reduce storage size");
        this.properties.put("compatibility", "WinUAE, Amiga Forever, E-UAE, 7-Zip, WinZip, The Unarchiver");
        this.properties.put("mime_type", "application/x-gzip");
        this.properties.put("platform_support", "Windows, macOS, Linux");
        this.properties.put("integrity", "CRC-32 checksum via GZIP");
    }

    public byte[] readADZ() throws IOException {
        try (FileInputStream fis = new FileInputStream(filepath);
             GZIPInputStream gis = new GZIPInputStream(fis)) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = gis.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesRead);
            }
            adfData = baos.toByteArray();
            fileSize = new File(filepath).length();
            // Extract CRC-32 and uncompressed size from GZIP footer
            try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
                raf.seek(fileSize - 8);
                crc32 = Integer.reverseBytes(raf.readInt());
                uncompressedSize = Integer.toUnsignedLong(Integer.reverseBytes(raf.readInt()));
            }
            return adfData;
        } catch (IOException e) {
            throw new IOException("Error reading .ADZ file: " + e.getMessage());
        }
    }

    public void writeADZ(String outputFilepath, byte[] adfData) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputFilepath);
             GZIPOutputStream gos = new GZIPOutputStream(fos)) {
            byte[] data = (adfData != null) ? adfData : this.adfData;
            if (data == null) {
                throw new IOException("No ADF data to write");
            }
            gos.write(data);
            gos.finish();
            this.filepath = outputFilepath;
            readADZ(); // Update properties after writing
        } catch (IOException e) {
            throw new IOException("Error writing .ADZ file: " + e.getMessage());
        }
    }

    public void printProperties() {
        System.out.println("ADZ File Properties:");
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
        System.out.println("  file_size: " + (fileSize > 0 ? fileSize + " bytes" : "Not loaded"));
        System.out.println("  uncompressed_size: " + (uncompressedSize > 0 ? uncompressedSize + " bytes" : "Not loaded"));
        System.out.println("  crc32: " + (crc32 != 0 ? String.format("0x%08X", crc32) : "Not loaded"));
    }

    public byte[] getAdfData() {
        return adfData;
    }

    public static void main(String[] args) {
        try {
            ADZFile adz = new ADZFile("example.adz");
            adz.readADZ();
            adz.printProperties();
            adz.writeADZ("output.adz", null);
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Explanation:

  • Reading: Uses GZIPInputStream to decompress the .ADZ file and extracts the ADF data. Reads the GZIP footer for CRC-32 and uncompressed size.
  • Writing: Uses GZIPOutputStream to create a new .ADZ file from ADF data.
  • Properties: Prints all properties, including dynamically computed file size, uncompressed size, and CRC-32.
  • Error Handling: Manages I/O and GZIP-specific exceptions.
  • Limitations: Does not parse the ADF structure, focusing on the GZIP layer.

4. JavaScript Class for .ADZ File Handling

This JavaScript class uses Node.js for file operations and the zlib module for GZIP handling. It’s designed to run in a Node.js environment.

const fs = require('fs').promises;
const zlib = require('zlib');
const util = require('util');
const inflate = util.promisify(zlib.gunzip);

class ADZFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {
            file_extension: '.adz',
            compression_type: 'GZIP',
            underlying_format: 'ADF',
            file_category: 'Disk Image, Compressed, Emulator',
            purpose: 'Store compressed Amiga disk image',
            size_efficiency: 'Compressed to reduce storage size',
            compatibility: 'WinUAE, Amiga Forever, E-UAE, 7-Zip, WinZip, The Unarchiver',
            mime_type: 'application/x-gzip',
            platform_support: 'Windows, macOS, Linux',
            integrity: 'CRC-32 checksum via GZIP'
        };
        this.adfData = null;
        this.fileSize = null;
        this.uncompressedSize = null;
        this.crc32 = null;
    }

    async readADZ() {
        try {
            const data = await fs.readFile(this.filepath);
            this.adfData = await inflate(data);
            this.fileSize = (await fs.stat(this.filepath)).size;
            // Extract GZIP footer (last 8 bytes)
            const footer = data.slice(-8);
            this.crc32 = footer.readUInt32LE(0);
            this.uncompressedSize = footer.readUInt32LE(4);
            return this.adfData;
        } catch (error) {
            throw new Error(`Error reading .ADZ file: ${error.message}`);
        }
    }

    async writeADZ(outputFilepath, adfData = null) {
        try {
            const data = adfData || this.adfData;
            if (!data) {
                throw new Error('No ADF data to write');
            }
            const compressed = await util.promisify(zlib.gzip)(data);
            await fs.writeFile(outputFilepath, compressed);
            this.filepath = outputFilepath;
            await this.readADZ(); // Update properties
        } catch (error) {
            throw new Error(`Error writing .ADZ file: ${error.message}`);
        }
    }

    printProperties() {
        console.log('ADZ File Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`  ${key}: ${value}`);
        }
        console.log(`  file_size: ${this.fileSize ? this.fileSize + ' bytes' : 'Not loaded'}`);
        console.log(`  uncompressed_size: ${this.uncompressedSize ? this.uncompressedSize + ' bytes' : 'Not loaded'}`);
        console.log(`  crc32: ${this.crc32 ? '0x' + this.crc32.toString(16).padStart(8, '0') : 'Not loaded'}`);
    }

    getAdfData() {
        return this.adfData;
    }
}

// Example usage
(async () => {
    try {
        const adz = new ADZFile('example.adz');
        await adz.readADZ();
        adz.printProperties();
        await adz.writeADZ('output.adz');
    } catch (error) {
        console.error(`Error: ${error.message}`);
    }
})();

Explanation:

  • Reading: Uses Node.js zlib.gunzip to decompress the .ADZ file and extracts the GZIP footer for CRC-32 and uncompressed size.
  • Writing: Uses zlib.gzip to compress ADF data into a new .ADZ file.
  • Properties: Prints all properties using console.log.
  • Environment: Requires Node.js; browser-based JavaScript would need a different approach (e.g., pako library for GZIP).
  • Limitations: Assumes Node.js environment; ADF parsing is not included.

5. C Class for .ADZ File Handling

In C, we use the zlib library for GZIP compression/decompression. This code defines a struct-based approach (C doesn't have classes, but we emulate the behavior).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

#define MAX_PROPERTY_LEN 256

typedef struct {
    char* filepath;
    struct {
        char file_extension[MAX_PROPERTY_LEN];
        char compression_type[MAX_PROPERTY_LEN];
        char underlying_format[MAX_PROPERTY_LEN];
        char file_category[MAX_PROPERTY_LEN];
        char purpose[MAX_PROPERTY_LEN];
        char size_efficiency[MAX_PROPERTY_LEN];
        char compatibility[MAX_PROPERTY_LEN];
        char mime_type[MAX_PROPERTY_LEN];
        char platform_support[MAX_PROPERTY_LEN];
        char integrity[MAX_PROPERTY_LEN];
    } properties;
    unsigned char* adf_data;
    size_t adf_data_len;
    size_t file_size;
    unsigned long uncompressed_size;
    unsigned long crc32;
} ADZFile;

void init_adz_file(ADZFile* adz, const char* filepath) {
    adz->filepath = strdup(filepath);
    strcpy(adz->properties.file_extension, ".adz");
    strcpy(adz->properties.compression_type, "GZIP");
    strcpy(adz->properties.underlying_format, "ADF");
    strcpy(adz->properties.file_category, "Disk Image, Compressed, Emulator");
    strcpy(adz->properties.purpose, "Store compressed Amiga disk image");
    strcpy(adz->properties.size_efficiency, "Compressed to reduce storage size");
    strcpy(adz->properties.compatibility, "WinUAE, Amiga Forever, E-UAE, 7-Zip, WinZip, The Unarchiver");
    strcpy(adz->properties.mime_type, "application/x-gzip");
    strcpy(adz->properties.platform_support, "Windows, macOS, Linux");
    strcpy(adz->properties.integrity, "CRC-32 checksum via GZIP");
    adz->adf_data = NULL;
    adz->adf_data_len = 0;
    adz->file_size = 0;
    adz->uncompressed_size = 0;
    adz->crc32 = 0;
}

int read_adz(ADZFile* adz) {
    gzFile file = gzopen(adz->filepath, "rb");
    if (!file) {
        fprintf(stderr, "Error: Cannot open .ADZ file: %s\n", adz->filepath);
        return 1;
    }

    // Read decompressed data
    unsigned char buffer[8192];
    size_t total = 0;
    adz->adf_data = NULL;
    while (1) {
        int bytes_read = gzread(file, buffer, sizeof(buffer));
        if (bytes_read <= 0) break;
        adz->adf_data = realloc(adz->adf_data, total + bytes_read);
        memcpy(adz->adf_data + total, buffer, bytes_read);
        total += bytes_read;
    }
    adz->adf_data_len = total;

    // Get file size
    FILE* raw_file = fopen(adz->filepath, "rb");
    if (raw_file) {
        fseek(raw_file, 0, SEEK_END);
        adz->file_size = ftell(raw_file);
        // Read GZIP footer
        fseek(raw_file, -8, SEEK_END);
        fread(&adz->crc32, sizeof(unsigned long), 1, raw_file);
        fread(&adz->uncompressed_size, sizeof(unsigned long), 1, raw_file);
        fclose(raw_file);
    }

    gzclose(file);
    return 0;
}

int write_adz(ADZFile* adz, const char* output_filepath, unsigned char* adf_data, size_t adf_data_len) {
    gzFile file = gzopen(output_filepath, "wb");
    if (!file) {
        fprintf(stderr, "Error: Cannot open output .ADZ file: %s\n", output_filepath);
        return 1;
    }

    unsigned char* data = adf_data ? adf_data : adz->adf_data;
    size_t len = adf_data ? adf_data_len : adz->adf_data_len;
    if (!data || len == 0) {
        fprintf(stderr, "Error: No ADF data to write\n");
        gzclose(file);
        return 1;
    }

    gzwrite(file, data, len);
    gzclose(file);
    free(adz->filepath);
    adz->filepath = strdup(output_filepath);
    return read_adz(adz); // Update properties
}

void print_properties(ADZFile* adz) {
    printf("ADZ File Properties:\n");
    printf("  file_extension: %s\n", adz->properties.file_extension);
    printf("  compression_type: %s\n", adz->properties.compression_type);
    printf("  underlying_format: %s\n", adz->properties.underlying_format);
    printf("  file_category: %s\n", adz->properties.file_category);
    printf("  purpose: %s\n", adz->properties.purpose);
    printf("  size_efficiency: %s\n", adz->properties.size_efficiency);
    printf("  compatibility: %s\n", adz->properties.compatibility);
    printf("  mime_type: %s\n", adz->properties.mime_type);
    printf("  platform_support: %s\n", adz->properties.platform_support);
    printf("  integrity: %s\n", adz->properties.integrity);
    printf("  file_size: %zu bytes\n", adz->file_size);
    printf("  uncompressed_size: %lu bytes\n", adz->uncompressed_size);
    printf("  crc32: 0x%08lX\n", adz->crc32);
}

void free_adz_file(ADZFile* adz) {
    free(adz->filepath);
    free(adz->adf_data);
}

int main() {
    ADZFile adz;
    init_adz_file(&adz, "example.adz");
    if (read_adz(&adz) == 0) {
        print_properties(&adz);
        write_adz(&adz, "output.adz", NULL, 0);
    }
    free_adz_file(&adz);
    return 0;
}

Explanation:

  • Reading: Uses zlib’s gzopen and gzread to decompress the .ADZ file. Extracts the GZIP footer manually.
  • Writing: Uses gzwrite to create a new .ADZ file.
  • Properties: Prints properties using printf.
  • Error Handling: Checks for file and memory allocation errors.
  • Compilation: Requires linking with -lz (e.g., gcc -o adz adz.c -lz).
  • Limitations: Does not parse ADF internals; focuses on GZIP layer.

Notes and Limitations

  • Lack of Detailed Specification: No official .ADZ specification exists beyond its definition as a GZIP-compressed ADF file. The code relies on the GZIP standard (RFC 1952) and assumes the ADF is a standard Amiga disk image.
  • ADF Parsing: The ADF format itself (within the .ADZ) is a raw disk image, often 880 KB, but parsing its filesystem (e.g., Amiga OFS/FFS) requires additional logic not included here, as it’s specific to Amiga emulators.
  • ZIP Variant: Some sources mention a ZIP-based .ADZ variant, but this is rare and not supported in the code above. If needed, ZIP handling could be added using libraries like minizip (C) or zipfile (Python).
  • Error Handling: Each implementation includes robust error handling for invalid files and I/O issues.
  • Dependencies: The JavaScript code requires Node.js, and the C code requires the zlib library. Python and Java use standard libraries.
  • Testing: The code assumes the presence of an example.adz file for demonstration. Replace with actual file paths for testing.

Sources:

  • Information about the .ADZ file format was gathered from various online resources describing it as a GZIP-compressed ADF file used by Amiga emulators.

If you need further details, such as parsing the ADF filesystem or handling the ZIP-based .ADZ variant, please let me know!

The .ADZ file format is a GZIP-compressed version of an Amiga Disk File (ADF), which is a raw sector-by-sector image of an Amiga floppy disk. The compression follows the standard GZIP specification, with no additional headers or modifications specific to .ADZ beyond the file extension. The uncompressed data is typically 901,120 bytes for a standard double-density Amiga floppy disk image (80 tracks × 2 heads × 11 sectors × 512 bytes per sector), or 1,802,240 bytes for high-density.

List of all the properties of this file format intrinsic to its file system:

  • Identification 1 (ID1): 1 byte, fixed value 0x1F.
  • Identification 2 (ID2): 1 byte, fixed value 0x8B.
  • Compression Method (CM): 1 byte, typically 8 for deflate.
  • Flags (FLG): 1 byte, bit field indicating presence of optional parts (FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT; bits 5-7 reserved).
  • Modification Time (MTIME): 4 bytes, Unix timestamp of original file modification (little-endian).
  • Extra Flags (XFL): 1 byte, compression-specific flags (e.g., 2 for maximum compression, 4 for fastest).
  • Operating System (OS): 1 byte, code for the OS where compression occurred (e.g., 0 for FAT, 1 for Amiga, 3 for Unix).
  • Extra Field Length (XLEN): 2 bytes (optional, if FEXTRA flag set), length of extra field data (little-endian).
  • Extra Field Data: Variable bytes (optional, if FEXTRA flag set), subfields with IDs and data.
  • Original File Name: Variable bytes (optional, if FNAME flag set), null-terminated string in ISO 8859-1.
  • File Comment: Variable bytes (optional, if FCOMMENT flag set), null-terminated string in ISO 8859-1.
  • Header CRC16 (HCRC): 2 bytes (optional, if FHCRC flag set), CRC16 of the header (little-endian).
  • Compressed Data: Variable bytes, the deflate-compressed payload (the ADF disk image data).
  • CRC32: 4 bytes, CRC32 checksum of the uncompressed data (little-endian).
  • Uncompressed Size (ISIZE): 4 bytes, size of uncompressed data modulo 2^32 (little-endian).

Python class:

import zlib
import struct

class ADZFile:
    def __init__(self, filename=None):
        self.id1 = 0x1F
        self.id2 = 0x8B
        self.cm = 8  # deflate
        self.flg = 0
        self.mtime = 0
        self.xfl = 0
        self.os = 0  # Default to FAT
        self.extra = b''
        self.filename = ''
        self.comment = ''
        self.hcrc = 0
        self.compressed_data = b''
        self.crc32 = 0
        self.isize = 0
        self.uncompressed_data = b''  # For convenience, store decompressed ADF data
        if filename:
            self.load(filename)

    def load(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        pos = 0
        self.id1, self.id2 = data[pos], data[pos+1]
        pos += 2
        if self.id1 != 0x1F or self.id2 != 0x8B:
            raise ValueError("Not a valid .ADZ (GZIP) file")
        self.cm = data[pos]
        pos += 1
        self.flg = data[pos]
        pos += 1
        self.mtime = struct.unpack('<I', data[pos:pos+4])[0]
        pos += 4
        self.xfl = data[pos]
        pos += 1
        self.os = data[pos]
        pos += 1
        if self.flg & 4:  # FEXTRA
            xlen = struct.unpack('<H', data[pos:pos+2])[0]
            pos += 2
            self.extra = data[pos:pos+xlen]
            pos += xlen
        if self.flg & 8:  # FNAME
            start = pos
            while data[pos] != 0:
                pos += 1
            self.filename = data[start:pos].decode('latin-1')
            pos += 1
        if self.flg & 16:  # FCOMMENT
            start = pos
            while data[pos] != 0:
                pos += 1
            self.comment = data[start:pos].decode('latin-1')
            pos += 1
        if self.flg & 2:  # FHCRC
            self.hcrc = struct.unpack('<H', data[pos:pos+2])[0]
            pos += 2
        compressed_start = pos
        total_len = len(data)
        self.crc32 = struct.unpack('<I', data[total_len-8:total_len-4])[0]
        self.isize = struct.unpack('<I', data[total_len-4:total_len])[0]
        self.compressed_data = data[compressed_start:total_len-8]
        # Decompress
        decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
        self.uncompressed_data = decompressor.decompress(self.compressed_data) + decompressor.flush()
        if len(self.uncompressed_data) % (1 << 32) != self.isize:
            raise ValueError("Uncompressed size mismatch")
        if zlib.crc32(self.uncompressed_data) & 0xFFFFFFFF != self.crc32:
            raise ValueError("CRC32 mismatch")

    def save(self, filename):
        header = bytearray()
        header.extend([self.id1, self.id2, self.cm, self.flg])
        header.extend(struct.pack('<I', self.mtime))
        header.extend([self.xfl, self.os])
        if self.flg & 4:
            if not self.extra:
                raise ValueError("Extra field expected but not set")
            header.extend(struct.pack('<H', len(self.extra)))
            header.extend(self.extra)
        if self.flg & 8:
            if not self.filename:
                raise ValueError("Filename expected but not set")
            header.extend(self.filename.encode('latin-1') + b'\x00')
        if self.flg & 16:
            if not self.comment:
                raise ValueError("Comment expected but not set")
            header.extend(self.comment.encode('latin-1') + b'\x00')
        temp_header = bytes(header)
        if self.flg & 2:
            self.hcrc = zlib.crc32(temp_header) & 0xFFFF
            header.extend(struct.pack('<H', self.hcrc))
        # Compress
        compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
        self.compressed_data = compressor.compress(self.uncompressed_data) + compressor.flush()
        self.crc32 = zlib.crc32(self.uncompressed_data) & 0xFFFFFFFF
        self.isize = len(self.uncompressed_data) % (1 << 32)
        # Write
        with open(filename, 'wb') as f:
            f.write(header + self.compressed_data + struct.pack('<I', self.crc32) + struct.pack('<I', self.isize))
  1. Java class:
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.zip.*;

public class ADZFile {
    private byte id1 = (byte) 0x1F;
    private byte id2 = (byte) 0x8B;
    private byte cm = 8; // deflate
    private byte flg = 0;
    private int mtime = 0;
    private byte xfl = 0;
    private byte os = 0; // Default to FAT
    private byte[] extra = new byte[0];
    private String filename = "";
    private String comment = "";
    private short hcrc = 0;
    private byte[] compressedData = new byte[0];
    private int crc32 = 0;
    private int isize = 0;
    private byte[] uncompressedData = new byte[0]; // For convenience, store decompressed ADF data

    public ADZFile(String filePath) throws IOException {
        if (filePath != null) {
            load(filePath);
        }
    }

    public void load(String filePath) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filePath));
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        id1 = bb.get();
        id2 = bb.get();
        if (id1 != (byte) 0x1F || id2 != (byte) 0x8B) {
            throw new IOException("Not a valid .ADZ (GZIP) file");
        }
        cm = bb.get();
        flg = bb.get();
        mtime = bb.getInt();
        xfl = bb.get();
        os = bb.get();
        if ((flg & 4) != 0) { // FEXTRA
            short xlen = bb.getShort();
            extra = new byte[xlen];
            bb.get(extra);
        }
        if ((flg & 8) != 0) { // FNAME
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte b;
            while ((b = bb.get()) != 0) {
                baos.write(b);
            }
            filename = baos.toString("ISO-8859-1");
        }
        if ((flg & 16) != 0) { // FCOMMENT
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte b;
            while ((b = bb.get()) != 0) {
                baos.write(b);
            }
            comment = baos.toString("ISO-8859-1");
        }
        if ((flg & 2) != 0) { // FHCRC
            hcrc = bb.getShort();
        }
        int headerEnd = bb.position();
        int totalLen = data.length;
        ByteBuffer tail = ByteBuffer.wrap(data, totalLen - 8, 8).order(ByteOrder.LITTLE_ENDIAN);
        crc32 = tail.getInt();
        isize = tail.getInt();
        compressedData = new byte[totalLen - 8 - headerEnd];
        System.arraycopy(data, headerEnd, compressedData, 0, compressedData.length);
        // Decompress
        Inflater inflater = new Inflater(true); // raw deflate
        inflater.setInput(compressedData);
        ByteArrayOutputStream decompressed = new ByteArrayOutputStream(compressedData.length * 2);
        byte[] buffer = new byte[1024];
        while (!inflater.finished()) {
            int count = inflater.inflate(buffer);
            decompressed.write(buffer, 0, count);
        }
        uncompressedData = decompressed.toByteArray();
        if (uncompressedData.length % (1L << 32) != isize) {
            throw new IOException("Uncompressed size mismatch");
        }
        CRC32 crc = new CRC32();
        crc.update(uncompressedData);
        if ((int) crc.getValue() != crc32) {
            throw new IOException("CRC32 mismatch");
        }
    }

    public void save(String filePath) throws IOException {
        ByteArrayOutputStream header = new ByteArrayOutputStream();
        header.write(id1);
        header.write(id2);
        header.write(cm);
        header.write(flg);
        ByteBuffer mtimeBuf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(mtime);
        header.write(mtimeBuf.array());
        header.write(xfl);
        header.write(os);
        if ((flg & 4) != 0) {
            if (extra.length == 0) {
                throw new IOException("Extra field expected but not set");
            }
            ByteBuffer xlenBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) extra.length);
            header.write(xlenBuf.array());
            header.write(extra);
        }
        if ((flg & 8) != 0) {
            if (filename.isEmpty()) {
                throw new IOException("Filename expected but not set");
            }
            header.write(filename.getBytes("ISO-8859-1"));
            header.write(0);
        }
        if ((flg & 16) != 0) {
            if (comment.isEmpty()) {
                throw new IOException("Comment expected but not set");
            }
            header.write(comment.getBytes("ISO-8859-1"));
            header.write(0);
        }
        byte[] tempHeader = header.toByteArray();
        if ((flg & 2) != 0) {
            CRC32 crc = new CRC32();
            crc.update(tempHeader);
            hcrc = (short) (crc.getValue() & 0xFFFF);
            ByteBuffer hcrcBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(hcrc);
            header.write(hcrcBuf.array());
        }
        // Compress
        Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true); // raw deflate
        deflater.setInput(uncompressedData);
        deflater.finish();
        ByteArrayOutputStream compressed = new ByteArrayOutputStream(uncompressedData.length);
        byte[] buffer = new byte[1024];
        while (!deflater.finished()) {
            int count = deflater.deflate(buffer);
            compressed.write(buffer, 0, count);
        }
        compressedData = compressed.toByteArray();
        CRC32 dataCrc = new CRC32();
        dataCrc.update(uncompressedData);
        crc32 = (int) dataCrc.getValue();
        isize = (int) (uncompressedData.length % (1L << 32));
        // Write
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(header.toByteArray());
            fos.write(compressedData);
            ByteBuffer tail = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putInt(crc32).putInt(isize);
            fos.write(tail.array());
        }
    }
}
  1. JavaScript class (Node.js):
const fs = require('fs');
const zlib = require('zlib');

class ADZFile {
    constructor(filename = null) {
        this.id1 = 0x1F;
        this.id2 = 0x8B;
        this.cm = 8; // deflate
        this.flg = 0;
        this.mtime = 0;
        this.xfl = 0;
        this.os = 0; // Default to FAT
        this.extra = Buffer.alloc(0);
        this.filename = '';
        this.comment = '';
        this.hcrc = 0;
        this.compressedData = Buffer.alloc(0);
        this.crc32 = 0;
        this.isize = 0;
        this.uncompressedData = Buffer.alloc(0); // For convenience, store decompressed ADF data
        if (filename) {
            this.load(filename);
        }
    }

    load(filename) {
        const data = fs.readFileSync(filename);
        let pos = 0;
        this.id1 = data[pos++];
        this.id2 = data[pos++];
        if (this.id1 !== 0x1F || this.id2 !== 0x8B) {
            throw new Error('Not a valid .ADZ (GZIP) file');
        }
        this.cm = data[pos++];
        this.flg = data[pos++];
        this.mtime = data.readUInt32LE(pos);
        pos += 4;
        this.xfl = data[pos++];
        this.os = data[pos++];
        if (this.flg & 4) { // FEXTRA
            const xlen = data.readUInt16LE(pos);
            pos += 2;
            this.extra = data.slice(pos, pos + xlen);
            pos += xlen;
        }
        if (this.flg & 8) { // FNAME
            let start = pos;
            while (data[pos] !== 0) pos++;
            this.filename = data.slice(start, pos).toString('latin1');
            pos++;
        }
        if (this.flg & 16) { // FCOMMENT
            let start = pos;
            while (data[pos] !== 0) pos++;
            this.comment = data.slice(start, pos).toString('latin1');
            pos++;
        }
        if (this.flg & 2) { // FHCRC
            this.hcrc = data.readUInt16LE(pos);
            pos += 2;
        }
        const compressedStart = pos;
        const totalLen = data.length;
        this.crc32 = data.readUInt32LE(totalLen - 8);
        this.isize = data.readUInt32LE(totalLen - 4);
        this.compressedData = data.slice(compressedStart, totalLen - 8);
        // Decompress
        this.uncompressedData = zlib.inflateRawSync(this.compressedData);
        if (this.uncompressedData.length % (2 ** 32) !== this.isize) {
            throw new Error('Uncompressed size mismatch');
        }
        if (zlib.crc32(this.uncompressedData) !== this.crc32) {
            throw new Error('CRC32 mismatch');
        }
    }

    save(filename) {
        let header = Buffer.alloc(10);
        header[0] = this.id1;
        header[1] = this.id2;
        header[2] = this.cm;
        header[3] = this.flg;
        header.writeUInt32LE(this.mtime, 4);
        header[8] = this.xfl;
        header[9] = this.os;
        if (this.flg & 4) {
            if (this.extra.length === 0) {
                throw new Error('Extra field expected but not set');
            }
            const xlenBuf = Buffer.alloc(2);
            xlenBuf.writeUInt16LE(this.extra.length, 0);
            header = Buffer.concat([header, xlenBuf, this.extra]);
        }
        if (this.flg & 8) {
            if (this.filename === '') {
                throw new Error('Filename expected but not set');
            }
            const fnameBuf = Buffer.from(this.filename, 'latin1');
            header = Buffer.concat([header, fnameBuf, Buffer.from([0])]);
        }
        if (this.flg & 16) {
            if (this.comment === '') {
                throw new Error('Comment expected but not set');
            }
            const commentBuf = Buffer.from(this.comment, 'latin1');
            header = Buffer.concat([header, commentBuf, Buffer.from([0])]);
        }
        let tempHeader = Buffer.from(header);
        if (this.flg & 2) {
            this.hcrc = zlib.crc32(tempHeader) & 0xFFFF;
            const hcrcBuf = Buffer.alloc(2);
            hcrcBuf.writeUInt16LE(this.hcrc, 0);
            header = Buffer.concat([header, hcrcBuf]);
        }
        // Compress
        this.compressedData = zlib.deflateRawSync(this.uncompressedData);
        this.crc32 = zlib.crc32(this.uncompressedData);
        this.isize = this.uncompressedData.length % (2 ** 32);
        const tail = Buffer.alloc(8);
        tail.writeUInt32LE(this.crc32, 0);
        tail.writeUInt32LE(this.isize, 4);
        // Write
        fs.writeFileSync(filename, Buffer.concat([header, this.compressedData, tail]));
    }
}
  1. C class (implemented as C++ class for object-oriented structure):
#include <fstream>
#include <vector>
#include <stdexcept>
#include <zlib.h>
#include <cstring>
#include <cstdint>

class ADZFile {
public:
    uint8_t id1 = 0x1F;
    uint8_t id2 = 0x8B;
    uint8_t cm = 8; // deflate
    uint8_t flg = 0;
    uint32_t mtime = 0;
    uint8_t xfl = 0;
    uint8_t os = 0; // Default to FAT
    std::vector<uint8_t> extra;
    std::string filename;
    std::string comment;
    uint16_t hcrc = 0;
    std::vector<uint8_t> compressed_data;
    uint32_t crc32 = 0;
    uint32_t isize = 0;
    std::vector<uint8_t> uncompressed_data; // For convenience, store decompressed ADF data

    ADZFile(const std::string& filepath = "") {
        if (!filepath.empty()) {
            load(filepath);
        }
    }

    void load(const std::string& filepath) {
        std::ifstream file(filepath, std::ios::binary | std::ios::ate);
        if (!file) {
            throw std::runtime_error("Failed to open file");
        }
        size_t size = file.tellg();
        file.seekg(0);
        std::vector<uint8_t> data(size);
        file.read(reinterpret_cast<char*>(data.data()), size);
        size_t pos = 0;
        id1 = data[pos++];
        id2 = data[pos++];
        if (id1 != 0x1F || id2 != 0x8B) {
            throw std::runtime_error("Not a valid .ADZ (GZIP) file");
        }
        cm = data[pos++];
        flg = data[pos++];
        mtime = *reinterpret_cast<uint32_t*>(&data[pos]);
        pos += 4;
        xfl = data[pos++];
        os = data[pos++];
        if (flg & 4) { // FEXTRA
            uint16_t xlen = *reinterpret_cast<uint16_t*>(&data[pos]);
            pos += 2;
            extra.assign(data.begin() + pos, data.begin() + pos + xlen);
            pos += xlen;
        }
        if (flg & 8) { // FNAME
            size_t start = pos;
            while (data[pos] != 0) ++pos;
            filename.assign(reinterpret_cast<char*>(&data[start]), pos - start);
            ++pos;
        }
        if (flg & 16) { // FCOMMENT
            size_t start = pos;
            while (data[pos] != 0) ++pos;
            comment.assign(reinterpret_cast<char*>(&data[start]), pos - start);
            ++pos;
        }
        if (flg & 2) { // FHCRC
            hcrc = *reinterpret_cast<uint16_t*>(&data[pos]);
            pos += 2;
        }
        size_t compressed_start = pos;
        crc32 = *reinterpret_cast<uint32_t*>(&data[size - 8]);
        isize = *reinterpret_cast<uint32_t*>(&data[size - 4]);
        size_t compressed_size = size - 8 - compressed_start;
        compressed_data.assign(data.begin() + compressed_start, data.begin() + compressed_start + compressed_size);
        // Decompress
        z_stream stream;
        memset(&stream, 0, sizeof(stream));
        if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) {
            throw std::runtime_error("inflateInit2 failed");
        }
        stream.next_in = compressed_data.data();
        stream.avail_in = compressed_data.size();
        std::vector<uint8_t> out(901120); // Initial buffer, resize as needed
        stream.next_out = out.data();
        stream.avail_out = out.size();
        int ret;
        do {
            ret = inflate(&stream, Z_FINISH);
            if (ret == Z_BUF_ERROR) {
                size_t used = stream.next_out - out.data();
                out.resize(out.size() * 2);
                stream.next_out = out.data() + used;
                stream.avail_out = out.size() - used;
            }
        } while (ret == Z_OK || ret == Z_BUF_ERROR);
        if (ret != Z_STREAM_END) {
            inflateEnd(&stream);
            throw std::runtime_error("Decompression failed");
        }
        uncompressed_data.assign(out.begin(), out.begin() + stream.total_out);
        inflateEnd(&stream);
        if (uncompressed_data.size() % (1ULL << 32) != isize) {
            throw std::runtime_error("Uncompressed size mismatch");
        }
        uint32_t computed_crc = crc32(0L, Z_NULL, 0);
        computed_crc = crc32(computed_crc, uncompressed_data.data(), uncompressed_data.size());
        if (computed_crc != crc32) {
            throw std::runtime_error("CRC32 mismatch");
        }
    }

    void save(const std::string& filepath) {
        std::vector<uint8_t> header;
        header.push_back(id1);
        header.push_back(id2);
        header.push_back(cm);
        header.push_back(flg);
        uint32_t mtime_le = mtime;
        header.insert(header.end(), reinterpret_cast<uint8_t*>(&mtime_le), reinterpret_cast<uint8_t*>(&mtime_le) + 4);
        header.push_back(xfl);
        header.push_back(os);
        if (flg & 4) {
            if (extra.empty()) {
                throw std::runtime_error("Extra field expected but not set");
            }
            uint16_t xlen = extra.size();
            header.insert(header.end(), reinterpret_cast<uint8_t*>(&xlen), reinterpret_cast<uint8_t*>(&xlen) + 2);
            header.insert(header.end(), extra.begin(), extra.end());
        }
        if (flg & 8) {
            if (filename.empty()) {
                throw std::runtime_error("Filename expected but not set");
            }
            header.insert(header.end(), filename.begin(), filename.end());
            header.push_back(0);
        }
        if (flg & 16) {
            if (comment.empty()) {
                throw std::runtime_error("Comment expected but not set");
            }
            header.insert(header.end(), comment.begin(), comment.end());
            header.push_back(0);
        }
        std::vector<uint8_t> temp_header = header;
        if (flg & 2) {
            uint32_t crc = crc32(0L, Z_NULL, 0);
            crc = crc32(crc, temp_header.data(), temp_header.size());
            hcrc = crc & 0xFFFF;
            header.insert(header.end(), reinterpret_cast<uint8_t*>(&hcrc), reinterpret_cast<uint8_t*>(&hcrc) + 2);
        }
        // Compress
        z_stream stream;
        memset(&stream, 0, sizeof(stream));
        if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
            throw std::runtime_error("deflateInit2 failed");
        }
        stream.next_in = uncompressed_data.data();
        stream.avail_in = uncompressed_data.size();
        std::vector<uint8_t> compressed( uncompressed_data.size() );
        stream.next_out = compressed.data();
        stream.avail_out = compressed.size();
        int ret = deflate(&stream, Z_FINISH);
        if (ret != Z_STREAM_END) {
            deflateEnd(&stream);
            throw std::runtime_error("Compression failed");
        }
        compressed_data.assign(compressed.begin(), compressed.begin() + stream.total_out);
        deflateEnd(&stream);
        uint32_t data_crc = crc32(0L, Z_NULL, 0);
        data_crc = crc32(data_crc, uncompressed_data.data(), uncompressed_data.size());
        crc32 = data_crc;
        isize = static_cast<uint32_t>(uncompressed_data.size() % (1ULL << 32));
        // Write
        std::ofstream file(filepath, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Failed to write file");
        }
        file.write(reinterpret_cast<char*>(header.data()), header.size());
        file.write(reinterpret_cast<char*>(compressed_data.data()), compressed_data.size());
        file.write(reinterpret_cast<char*>(&crc32), 4);
        file.write(reinterpret_cast<char*>(&isize), 4);
    }
};