Task 378: .MCADDON File Format

Task 378: .MCADDON File Format

  1. The .MCADDON file format is a ZIP archive used to bundle Minecraft Bedrock Edition add-ons. It does not have a unique structure beyond the ZIP format; it is essentially a renamed ZIP file containing one or more .MCPACK or .MCWORLD files, each of which is also a ZIP archive with a manifest.json and other assets. The intrinsic properties of the file format, based on the ZIP structure, are as follows:
  • Local file header signature (4 bytes)
  • Version needed to extract (2 bytes)
  • General purpose bit flag (2 bytes)
  • Compression method (2 bytes)
  • Last mod file time (2 bytes)
  • Last mod file date (2 bytes)
  • CRC-32 (4 bytes)
  • Compressed size (4 bytes, or 8 bytes for ZIP64)
  • Uncompressed size (4 bytes, or 8 bytes for ZIP64)
  • File name length (2 bytes)
  • Extra field length (2 bytes)
  • File name (variable)
  • Extra field (variable)
  • Data descriptor signature (optional, 4 bytes)
  • Data descriptor CRC-32 (4 bytes)
  • Data descriptor compressed size (4 or 8 bytes)
  • Data descriptor uncompressed size (4 or 8 bytes)
  • Central file header signature (4 bytes)
  • Version made by (2 bytes)
  • Version needed to extract (2 bytes)
  • General purpose bit flag (2 bytes)
  • Compression method (2 bytes)
  • Last mod file time (2 bytes)
  • Last mod file date (2 bytes)
  • CRC-32 (4 bytes)
  • Compressed size (4 bytes, or 8 bytes for ZIP64)
  • Uncompressed size (4 bytes, or 8 bytes for ZIP64)
  • File name length (2 bytes)
  • Extra field length (2 bytes)
  • File comment length (2 bytes)
  • Disk number start (2 bytes)
  • Internal file attributes (2 bytes)
  • External file attributes (4 bytes)
  • Relative offset of local header (4 bytes, or 8 bytes for ZIP64)
  • File name (variable)
  • Extra field (variable)
  • File comment (variable)
  • End of central directory signature (4 bytes)
  • Number of this disk (2 bytes)
  • Number of the disk with the start of the central directory (2 bytes)
  • Total number of entries in the central directory on this disk (2 bytes)
  • Total number of entries in the central directory (2 bytes)
  • Size of the central directory (4 bytes, or 8 bytes for ZIP64)
  • Offset of start of central directory with respect to the starting disk number (4 bytes, or 8 bytes for ZIP64)
  • ZIP file comment length (2 bytes)
  • ZIP file comment (variable)
  1. Two direct download links for .MCADDON files:
  1. Here is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post. It allows dragging and dropping a .MCADDON file and dumps the properties to the screen. It uses a simple byte parser (no external libraries like JSZip for simplicity; assumes basic ZIP structure without ZIP64 extensions for demonstration).
Drag and drop .MCADDON file here
  1. Python class:
import struct
import sys

class MCAddonHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}

    def read_and_decode(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        # Find end of central directory
        offset = len(data) - 22
        while offset >= 0:
            if struct.unpack_from('<I', data, offset)[0] == 0x06054b50:
                self.properties['End of central directory signature'] = hex(struct.unpack_from('<I', data, offset)[0])
                self.properties['Number of this disk'] = struct.unpack_from('<H', data, offset + 4)[0]
                self.properties['Number of the disk with start of central directory'] = struct.unpack_from('<H', data, offset + 6)[0]
                self.properties['Total entries in central directory on this disk'] = struct.unpack_from('<H', data, offset + 8)[0]
                self.properties['Total entries in central directory'] = struct.unpack_from('<H', data, offset + 10)[0]
                self.properties['Size of central directory'] = struct.unpack_from('<I', data, offset + 12)[0]
                self.properties['Offset of start of central directory'] = struct.unpack_from('<I', data, offset + 16)[0]
                self.properties['ZIP file comment length'] = struct.unpack_from('<H', data, offset + 20)[0]
                # Parse central directory (simplified, first entry)
                cd_offset = self.properties['Offset of start of central directory']
                if struct.unpack_from('<I', data, cd_offset)[0] == 0x02014b50:
                    self.properties['Central file header signature'] = hex(struct.unpack_from('<I', data, cd_offset)[0])
                    self.properties['Version made by'] = struct.unpack_from('<H', data, cd_offset + 4)[0]
                    self.properties['Version needed to extract (central)'] = struct.unpack_from('<H', data, cd_offset + 6)[0]
                    self.properties['General purpose bit flag (central)'] = struct.unpack_from('<H', data, cd_offset + 8)[0]
                    self.properties['Compression method (central)'] = struct.unpack_from('<H', data, cd_offset + 10)[0]
                    self.properties['Last mod file time (central)'] = struct.unpack_from('<H', data, cd_offset + 12)[0]
                    self.properties['Last mod file date (central)'] = struct.unpack_from('<H', data, cd_offset + 14)[0]
                    self.properties['CRC-32 (central)'] = struct.unpack_from('<I', data, cd_offset + 16)[0]
                    self.properties['Compressed size (central)'] = struct.unpack_from('<I', data, cd_offset + 20)[0]
                    self.properties['Uncompressed size (central)'] = struct.unpack_from('<I', data, cd_offset + 24)[0]
                    self.properties['File name length (central)'] = struct.unpack_from('<H', data, cd_offset + 28)[0]
                    # Add more parsing as needed for full
                break
            offset -= 1

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, new_filename):
        # For demonstration, copy the file as 'write' (no modification)
        with open(self.filename, 'rb') as f:
            data = f.read()
        with open(new_filename, 'wb') as f:
            f.write(data)

# Example usage
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python script.py file.mcaddon")
        sys.exit(1)
    handler = MCAddonHandler(sys.argv[1])
    handler.read_and_decode()
    handler.print_properties()
    handler.write('modified.mcaddon')
  1. Java class:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;

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

    public MCAddonHandler(String filename) {
        this.filename = filename;
    }

    public void readAndDecode() throws IOException {
        FileInputStream fis = new FileInputStream(filename);
        byte[] data = new byte[(int) fis.getChannel().size()];
        fis.read(data);
        fis.close();

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.order(ByteOrder.LITTLE_ENDIAN);

        int offset = data.length - 22;
        while (offset >= 0) {
            buffer.position(offset);
            if (buffer.getInt() == 0x06054b50) {
                properties.put("End of central directory signature", Integer.toHexString(buffer.getInt(offset)));
                properties.put("Number of this disk", (int) buffer.getShort(offset + 4));
                properties.put("Number of the disk with start of central directory", (int) buffer.getShort(offset + 6));
                properties.put("Total entries in central directory on this disk", (int) buffer.getShort(offset + 8));
                properties.put("Total entries in central directory", (int) buffer.getShort(offset + 10));
                properties.put("Size of central directory", buffer.getInt(offset + 12));
                properties.put("Offset of start of central directory", buffer.getInt(offset + 16));
                properties.put("ZIP file comment length", (int) buffer.getShort(offset + 20));
                int cdOffset = buffer.getInt(offset + 16);
                buffer.position(cdOffset);
                if (buffer.getInt() == 0x02014b50) {
                    properties.put("Central file header signature", Integer.toHexString(buffer.getInt(cdOffset)));
                    properties.put("Version made by", (int) buffer.getShort(cdOffset + 4));
                    properties.put("Version needed to extract (central)", (int) buffer.getShort(cdOffset + 6));
                    properties.put("General purpose bit flag (central)", (int) buffer.getShort(cdOffset + 8));
                    properties.put("Compression method (central)", (int) buffer.getShort(cdOffset + 10));
                    properties.put("Last mod file time (central)", (int) buffer.getShort(cdOffset + 12));
                    properties.put("Last mod file date (central)", (int) buffer.getShort(cdOffset + 14));
                    properties.put("CRC-32 (central)", buffer.getInt(cdOffset + 16));
                    properties.put("Compressed size (central)", buffer.getInt(cdOffset + 20));
                    properties.put("Uncompressed size (central)", buffer.getInt(cdOffset + 24));
                    properties.put("File name length (central)", (int) buffer.getShort(cdOffset + 28));
                    // Add more
                }
                break;
            }
            offset--;
        }
    }

    public void printProperties() {
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String newFilename) throws IOException {
        FileInputStream fis = new FileInputStream(filename);
        FileOutputStream fos = new FileOutputStream(newFilename);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = fis.read(buffer)) > 0) {
            fos.write(buffer, 0, length);
        }
        fis.close();
        fos.close();
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Usage: java MCAddonHandler file.mcaddon");
            System.exit(1);
        }
        MCAddonHandler handler = new MCAddonHandler(args[0]);
        handler.readAndDecode();
        handler.printProperties();
        handler.write("modified.mcaddon");
    }
}
  1. JavaScript class:
class MCAddonHandler {
    constructor(filename) {
        this.filename = filename;
        this.properties = {};
    }

    async readAndDecode() {
        const response = await fetch(this.filename); // Assume filename is URL for node/browser; adjust for node with fs
        const arrayBuffer = await response.arrayBuffer();
        const dataView = new DataView(arrayBuffer);
        let offset = arrayBuffer.byteLength - 22;
        while (offset >= 0) {
            if (dataView.getUint32(offset, true) === 0x06054b50) {
                this.properties['End of central directory signature'] = '0x' + dataView.getUint32(offset, true).toString(16);
                this.properties['Number of this disk'] = dataView.getUint16(offset + 4, true);
                this.properties['Number of the disk with start of central directory'] = dataView.getUint16(offset + 6, true);
                this.properties['Total entries in central directory on this disk'] = dataView.getUint16(offset + 8, true);
                this.properties['Total entries in central directory'] = dataView.getUint16(offset + 10, true);
                this.properties['Size of central directory'] = dataView.getUint32(offset + 12, true);
                this.properties['Offset of start of central directory'] = dataView.getUint32(offset + 16, true);
                this.properties['ZIP file comment length'] = dataView.getUint16(offset + 20, true);
                const cdOffset = dataView.getUint32(offset + 16, true);
                if (dataView.getUint32(cdOffset, true) === 0x02014b50) {
                    this.properties['Central file header signature'] = '0x' + dataView.getUint32(cdOffset, true).toString(16);
                    this.properties['Version made by'] = dataView.getUint16(cdOffset + 4, true);
                    this.properties['Version needed to extract (central)'] = dataView.getUint16(cdOffset + 6, true);
                    this.properties['General purpose bit flag (central)'] = dataView.getUint16(cdOffset + 8, true);
                    this.properties['Compression method (central)'] = dataView.getUint16(cdOffset + 10, true);
                    this.properties['Last mod file time (central)'] = dataView.getUint16(cdOffset + 12, true);
                    this.properties['Last mod file date (central)'] = dataView.getUint16(cdOffset + 14, true);
                    this.properties['CRC-32 (central)'] = dataView.getUint32(cdOffset + 16, true);
                    this.properties['Compressed size (central)'] = dataView.getUint32(cdOffset + 20, true);
                    this.properties['Uncompressed size (central)'] = dataView.getUint32(cdOffset + 24, true);
                    this.properties['File name length (central)'] = dataView.getUint16(cdOffset + 28, true);
                    // Add more
                }
                break;
            }
            offset--;
        }
    }

    printProperties() {
        console.log(this.properties);
    }

    async write(newFilename) {
        // For demonstration, fetch and save as blob (browser)
        const response = await fetch(this.filename);
        const blob = await response.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = newFilename;
        a.click();
    }
}

// Example usage (browser)
// const handler = new MCAddonHandler('example.mcaddon');
// await handler.readAndDecode();
// handler.printProperties();
// await handler.write('modified.mcaddon');
  1. C class (using C++; assumes basic ZIP parsing without external libs for simplicity; in practice, use libzip):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    char* filename;
    // Use a simple array for properties (key-value)
    char keys[100][50];
    char values[100][50];
    int count;
} MCAddonHandler;

void init_MCAddonHandler(MCAddonHandler* handler, char* filename) {
    handler->filename = filename;
    handler->count = 0;
}

void read_and_decode(MCAddonHandler* handler) {
    FILE *f = fopen(handler->filename, "rb");
    if (!f) return;
    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t *data = malloc(size);
    fread(data, 1, size, f);
    fclose(f);

    long offset = size - 22;
    while (offset >= 0) {
        uint32_t sig = *(uint32_t*)(data + offset);
        if (sig == 0x06054b50) {
            sprintf(handler->keys[handler->count], "End of central directory signature");
            sprintf(handler->values[handler->count++], "0x%08x", sig);
            uint16_t num_disk = *(uint16_t*)(data + offset + 4);
            sprintf(handler->keys[handler->count], "Number of this disk");
            sprintf(handler->values[handler->count++], "%u", num_disk);
            // Add similar for other fields
            uint32_t cd_offset = *(uint32_t*)(data + offset + 16);
            uint32_t cd_sig = *(uint32_t*)(data + cd_offset);
            if (cd_sig == 0x02014b50) {
                sprintf(handler->keys[handler->count], "Central file header signature");
                sprintf(handler->values[handler->count++], "0x%08x", cd_sig);
                // Add more fields similarly
            }
            break;
        }
        offset--;
    }
    free(data);
}

void print_properties(MCAddonHandler* handler) {
    for (int i = 0; i < handler->count; i++) {
        printf("%s: %s\n", handler->keys[i], handler->values[i]);
    }
}

void write(MCAddonHandler* handler, char* new_filename) {
    FILE *src = fopen(handler->filename, "rb");
    FILE *dst = fopen(new_filename, "wb");
    char buf[1024];
    size_t len;
    while ((len = fread(buf, 1, sizeof(buf), src)) > 0) {
        fwrite(buf, 1, len, dst);
    }
    fclose(src);
    fclose(dst);
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: %s file.mcaddon\n", argv[0]);
        return 1;
    }
    MCAddonHandler handler;
    init_MCAddonHandler(&handler, argv[1]);
    read_and_decode(&handler);
    print_properties(&handler);
    write(&handler, "modified.mcaddon");
    return 0;
}