Task 378: .MCADDON File Format
Task 378: .MCADDON File Format
- 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)
- Two direct download links for .MCADDON files:
- https://github.com/ForestOfLight/Canopy/releases/download/v1.4.0/Canopy-v1.4.0.mcaddon
- https://github.com/SIsilicon/WorldEdit-BE/releases/download/v0.10.1/WorldEdit.mcaddon
- 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
- 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')
- 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");
}
}
- 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');
- 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;
}