Task 384: .MCTEMPLATE File Format

Task 384: .MCTEMPLATE File Format

.MCTEMPLATE File Format Specifications

The .MCTEMPLATE file format is used for Minecraft Bedrock Edition world templates. It is essentially a ZIP archive (using standard ZIP compression) renamed with the .mctemplate extension. The format contains a specific set of files and folders that define a pre-configured world template for the game. The specifications are based on official Minecraft documentation from Microsoft, requiring a valid manifest.json for importability into the game. The format is not a proprietary binary structure but a container for game data files.

1. List of All Properties Intrinsic to This File Format

The "properties" refer to the internal structure, required/optional files, and key elements that make up the file format. These are intrinsic to how the file is organized on the file system (as a ZIP container) and how Minecraft interprets it. The format has no custom header or binary metadata outside of standard ZIP; all properties are derived from the contained files and their contents. Here's the comprehensive list:

ZIP Container Properties:

  • Compression method: Deflate (standard ZIP compression).
  • File extension: .mctemplate (renamed from .zip to indicate it's a Minecraft world template).
  • MIME type: application/zip (implicit, as it's a ZIP file).
  • Root level files/folders: No extra nesting; files are zipped directly (zipping a folder would create an invalid structure for import).

Required Files and Their Properties:

  • manifest.json: A JSON file at the root defining the template metadata.
  • Structure (format_version: 2):
  • header: Object with name ("pack.name"), description ("pack.description"), version (array e.g. [1, 0, 0]), uuid (string, unique UUID).
  • modules: Array of objects, each with version (array e.g. [1, 0, 0]), type ("world_template"), uuid (string, unique UUID different from header's).
  • Optional fields: allow_random_seed (boolean, true to enable random seeds; requires deleting the db folder).
  • level.dat: NBT (Named Binary Tag) file containing world data like seed, game rules, player spawn, etc. Compressed with GZip.
  • Properties: Binary structure with root compound tag including LevelName, SpawnX/Y/Z, GameType, etc.

Optional but Common Files and Folders:

  • texts folder: Contains localization files.
  • en_US.lang: Text file with key-value pairs, e.g., "pack.name=Template Name\npack.description=Template Description".
  • languages.json: JSON array listing supported languages, e.g., ["en_US"] (English required; others like "es_ES" for multi-language support).
  • world_icon.jpeg: JPEG image (800x450 pixels recommended) for the template icon in the game UI.
  • db folder: LevelDB database folder containing chunk data (world terrain, entities, etc.).
  • Properties: Binary key-value store; files like 000001.ldb, LOG, MANIFEST, etc.
  • Add-On Related Files (if template includes packs):
  • behavior_packs folder: Contains add-on behavior packs (short folder names ≤10 chars to avoid path issues).
  • resource_packs folder: Contains add-on resource packs (similar naming constraint).
  • world_behavior_pack_history.json: JSON history of applied behavior packs.
  • world_behavior_packs.json: JSON list of current behavior packs.
  • world_resource_pack_history.json: JSON history of applied resource packs.
  • world_resource_packs.json: JSON list of current resource packs.

These properties ensure the template can be imported and used to generate new worlds in Minecraft Bedrock Edition. Invalid structures (e.g., missing manifest.json or extra folder levels) prevent import.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .MCTEMPLATE File Dump

This is a self-contained HTML page with embedded JavaScript that allows dragging and dropping a .mctemplate file. It uses JSZip to unzip the file, parses the key files (manifest.json, en_US.lang, etc.), and dumps the properties to the screen. Include JSZip library via CDN for simplicity. This can be embedded in a Ghost blog post as raw HTML.

.MCTEMPLATE Property Dumper
Drag and drop .mctemplate file here

4. Python Class for .MCTEMPLATE Handling

This Python class uses zipfile to open the file, extracts and parses properties, and supports reading, writing (creating a new .mctemplate from properties), and printing.

import zipfile
import json
import os
from io import BytesIO
import zlib  # For potential NBT, but simplified here

class MCTemplateHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}
        self._read()

    def _read(self):
        with zipfile.ZipFile(self.filepath, 'r') as zf:
            # ZIP properties
            self.properties['zip_info'] = {
                'filename': os.path.basename(self.filepath),
                'size': os.path.getsize(self.filepath)
            }

            # manifest.json
            if 'manifest.json' in zf.namelist():
                with zf.open('manifest.json') as f:
                    self.properties['manifest'] = json.load(f)

            # texts/en_US.lang
            if 'texts/en_US.lang' in zf.namelist():
                with zf.open('texts/en_US.lang') as f:
                    self.properties['en_US_lang'] = f.read().decode('utf-8')

            # languages.json
            if 'texts/languages.json' in zf.namelist():
                with zf.open('texts/languages.json') as f:
                    self.properties['languages'] = json.load(f)

            # world_icon.jpeg
            if 'world_icon.jpeg' in zf.namelist():
                with zf.open('world_icon.jpeg') as f:
                    self.properties['world_icon'] = {'size': len(f.read())}

            # level.dat (simple check, NBT parsing omitted for brevity)
            if 'level.dat' in zf.namelist():
                self.properties['level_dat'] = 'Present'

            # db folder
            db_files = [f for f in zf.namelist() if f.startswith('db/')]
            if db_files:
                self.properties['db'] = {'file_count': len(db_files)}

            # Add-on files
            addon_keys = ['behavior_packs/', 'resource_packs/', 'world_behavior_pack_history.json', 'world_behavior_packs.json', 'world_resource_pack_history.json', 'world_resource_packs.json']
            for key in addon_keys:
                if any(f.startswith(key) for f in zf.namelist()) or key in zf.namelist():
                    self.properties[key.strip('/')] = 'Present'

    def print_properties(self):
        print(json.dumps(self.properties, indent=4))

    def write(self, new_filepath):
        with zipfile.ZipFile(new_filepath, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
            # Write manifest
            if 'manifest' in self.properties:
                zf.writestr('manifest.json', json.dumps(self.properties['manifest']))

            # Write en_US.lang
            if 'en_US_lang' in self.properties:
                zf.writestr('texts/en_US.lang', self.properties['en_US_lang'])

            # Write languages.json
            if 'languages' in self.properties:
                zf.writestr('texts/languages.json', json.dumps(self.properties['languages']))

            # Other files would require full data; simplified to key ones
            # For full write, extract all in read and re-zip

# Example usage
# handler = MCTemplateHandler('example.mctemplate')
# handler.print_properties()
# handler.write('new.mctemplate')

5. Java Class for .MCTEMPLATE Handling

This Java class uses java.util.zip to handle the ZIP, parses properties, supports read/write/print.

import java.io.*;
import java.util.*;
import java.util.zip.*;
import org.json.simple.*;
import org.json.simple.parser.*;

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

    public MCTemplateHandler(String filepath) {
        this.filepath = filepath;
        read();
    }

    private void read() {
        try (ZipFile zf = new ZipFile(filepath)) {
            // ZIP properties
            properties.put("zip_info", Map.of("filename", new File(filepath).getName(), "size", new File(filepath).length()));

            Enumeration<? extends ZipEntry> entries = zf.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String name = entry.getName();

                if (name.equals("manifest.json")) {
                    try (InputStream is = zf.getInputStream(entry)) {
                        properties.put("manifest", JSONValue.parse(new InputStreamReader(is)));
                    }
                } else if (name.equals("texts/en_US.lang")) {
                    try (BufferedReader br = new BufferedReader(new InputStreamReader(zf.getInputStream(entry)))) {
                        StringBuilder sb = new StringBuilder();
                        String line;
                        while ((line = br.readLine()) != null) sb.append(line).append("\n");
                        properties.put("en_US_lang", sb.toString());
                    }
                } else if (name.equals("texts/languages.json")) {
                    try (InputStream is = zf.getInputStream(entry)) {
                        properties.put("languages", JSONValue.parse(new InputStreamReader(is)));
                    }
                } else if (name.equals("world_icon.jpeg")) {
                    properties.put("world_icon", Map.of("size", entry.getSize()));
                } else if (name.equals("level.dat")) {
                    properties.put("level_dat", "Present");
                } else if (name.startsWith("db/")) {
                    List<String> dbFiles = (List<String>) properties.getOrDefault("db_files", new ArrayList<>());
                    dbFiles.add(name);
                    properties.put("db_files", dbFiles);
                } else if (name.startsWith("behavior_packs/") || name.equals("world_behavior_pack_history.json") /* add others */) {
                    properties.put(name.split("/")[0], "Present");
                }
            }
            if (properties.containsKey("db_files")) {
                properties.put("db", Map.of("file_count", ((List) properties.get("db_files")).size()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void printProperties() {
        System.out.println(JSONValue.toJSONString(properties));
    }

    public void write(String newFilepath) {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(newFilepath))) {
            if (properties.containsKey("manifest")) {
                ZipEntry ze = new ZipEntry("manifest.json");
                zos.putNextEntry(ze);
                zos.write(JSONValue.toJSONString(properties.get("manifest")).getBytes());
                zos.closeEntry();
            }
            if (properties.containsKey("en_US_lang")) {
                ZipEntry ze = new ZipEntry("texts/en_US.lang");
                zos.putNextEntry(ze);
                zos.write(((String) properties.get("en_US_lang")).getBytes());
                zos.closeEntry();
            }
            // Add similar for other files; full implementation would copy all data
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Example usage
    // public static void main(String[] args) {
    //     MCTemplateHandler handler = new MCTemplateHandler("example.mctemplate");
    //     handler.printProperties();
    //     handler.write("new.mctemplate");
    // }
}

(Note: Requires json-simple library for JSON handling.)

6. JavaScript Class for .MCTEMPLATE Handling

This JS class uses JSZip for handling in browser/node. For node, require 'jszip' and 'fs'.

const JSZip = require('jszip'); // For Node.js; in browser use CDN
const fs = require('fs'); // For Node.js

class MCTemplateHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
        this.read();
    }

    async read() {
        const data = fs.readFileSync(this.filepath);
        const zip = await JSZip.loadAsync(data);

        // ZIP properties
        this.properties.zip_info = { filename: this.filepath.split('/').pop(), size: data.length };

        // manifest.json
        if (zip.file('manifest.json')) {
            const content = await zip.file('manifest.json').async('string');
            this.properties.manifest = JSON.parse(content);
        }

        // texts/en_US.lang
        if (zip.file('texts/en_US.lang')) {
            this.properties.en_US_lang = await zip.file('texts/en_US.lang').async('string');
        }

        // languages.json
        if (zip.file('texts/languages.json')) {
            const content = await zip.file('texts/languages.json').async('string');
            this.properties.languages = JSON.parse(content);
        }

        // world_icon.jpeg
        if (zip.file('world_icon.jpeg')) {
            const icon = await zip.file('world_icon.jpeg').async('nodebuffer');
            this.properties.world_icon = { size: icon.length };
        }

        // level.dat
        if (zip.file('level.dat')) {
            this.properties.level_dat = 'Present';
        }

        // db folder
        const dbFiles = Object.keys(zip.files).filter(f => f.startsWith('db/'));
        if (dbFiles.length) {
            this.properties.db = { file_count: dbFiles.length };
        }

        // Add-on files
        const addonKeys = ['behavior_packs/', 'resource_packs/', 'world_behavior_pack_history.json', 'world_behavior_packs.json', 'world_resource_pack_history.json', 'world_resource_packs.json'];
        addonKeys.forEach(key => {
            if (Object.keys(zip.files).some(f => f.startsWith(key) || f === key)) {
                this.properties[key.replace('/', '')] = 'Present';
            }
        });
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    async write(newFilepath) {
        const zip = new JSZip();
        if (this.properties.manifest) {
            zip.file('manifest.json', JSON.stringify(this.properties.manifest));
        }
        if (this.properties.en_US_lang) {
            zip.file('texts/en_US.lang', this.properties.en_US_lang);
        }
        if (this.properties.languages) {
            zip.file('texts/languages.json', JSON.stringify(this.properties.languages));
        }
        // Add others similarly; for full, need all data stored

        const content = await zip.generateAsync({ type: 'nodebuffer' });
        fs.writeFileSync(newFilepath, content);
    }
}

// Example usage (Node.js)
// const handler = new MCTemplateHandler('example.mctemplate');
// handler.printProperties();
// await handler.write('new.mctemplate');

7. C Class for .MCTEMPLATE Handling

This is in C++ (as "c class" likely means C++ for class support). Uses minizip or similar for ZIP; here assuming zlib/minizip included. Simplified parsing (JSON/NBT require libs like cJSON, nbt++).

#include <iostream>
#include <string>
#include <map>
#include <fstream>
#include <zip.h> // Assume minizip library
#include <cjson/cJSON.h> // For JSON parsing

class MCTemplateHandler {
private:
    std::string filepath;
    std::map<std::string, std::string> properties; // Simplified to string values

public:
    MCTemplateHandler(const std::string& fp) : filepath(fp) {
        read();
    }

    void read() {
        zip_t* z = zip_open(filepath.c_str(), ZIP_RDONLY, nullptr);
        if (!z) return;

        // ZIP properties
        struct stat st;
        stat(filepath.c_str(), &st);
        properties["zip_filename"] = filepath.substr(filepath.find_last_of("/\\") + 1);
        properties["zip_size"] = std::to_string(st.st_size);

        int num_entries = zip_get_num_entries(z, 0);
        for (int i = 0; i < num_entries; ++i) {
            const char* name = zip_get_name(z, i, 0);
            if (std::string(name) == "manifest.json") {
                zip_file_t* f = zip_fopen_index(z, i, 0);
                char buf[4096];
                zip_fread(f, buf, sizeof(buf));
                zip_fclose(f);
                cJSON* json = cJSON_Parse(buf);
                properties["manifest"] = cJSON_Print(json);
                cJSON_Delete(json);
            } else if (std::string(name) == "texts/en_US.lang") {
                zip_file_t* f = zip_fopen_index(z, i, 0);
                char buf[4096];
                zip_fread(f, buf, sizeof(buf));
                zip_fclose(f);
                properties["en_US_lang"] = buf;
            } // Add similar for others, check existence for level.dat, db, etc.
        }

        zip_close(z);
    }

    void printProperties() {
        for (const auto& p : properties) {
            std::cout << p.first << ": " << p.second << std::endl;
        }
    }

    void write(const std::string& newFilepath) {
        zip_t* z = zip_open(newFilepath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, nullptr);
        if (!z) return;

        if (properties.find("manifest") != properties.end()) {
            zip_source_t* src = zip_source_buffer(z, properties["manifest"].c_str(), properties["manifest"].size(), 0);
            zip_file_add(z, "manifest.json", src, ZIP_FL_OVERWRITE);
        }
        // Add similar for other files

        zip_close(z);
    }
};

// Example usage
// int main() {
//     MCTemplateHandler handler("example.mctemplate");
//     handler.printProperties();
//     handler.write("new.mctemplate");
//     return 0;
// }

(Note: Requires minizip and cJSON libraries; full NBT parsing would need additional libs.)