Task 838: .XMF File Format

Task 838: .XMF File Format

1. Properties of the .XMF File Format

The .XMF (Extensible Music Format) is a binary, tree-structured container format designed to bundle MIDI sequences (Standard MIDI Files or SMF), Downloadable Sounds (DLS) instruments, and optionally WAVE audio data or other resources. It ensures consistent playback across devices by encapsulating or referencing these elements. The format is not XML-based, despite the extension similarity. It supports versions 1.00, 1.01, and 2.00, with later versions adding features such as compression (zlib), audio clips, and ID3 metadata.

The intrinsic properties refer to the structural elements defined in the file format specification, including headers, fields, and data organization. These are binary-encoded, with most integer values using Variable-Length Quantity (VLQ) encoding for compactness. The file consists of a header followed by a tree of nodes in preorder traversal (depth-first). Nodes can be folders (containing children) or content nodes (holding data streams like MIDI or DLS).

Key properties:

  • Magic Number: 4 bytes, always "XMF_" (hex: 58 4D 46 5F).
  • Version: 4 bytes, ASCII string such as "1.00", "1.01", or "2.00".
  • File Type ID: 4-byte integer (present only in version 2.00 and later), indicating the specific XMF type (e.g., Type 0, Type 1 for standard; Type 2 for Mobile XMF).
  • File Type Revision ID: 4-byte integer (present only in version 2.00 and later), specifying the revision of the file type.
  • File Size: VLQ-encoded integer, representing the total size of the file in bytes.
  • Metadata Types Table (MTT) Length: VLQ-encoded integer, indicating the length of the MTT in bytes.
  • MTT Contents: Binary data of the specified length, defining metadata types used in the file (e.g., for technical, copyright, or descriptive information like title and author).
  • Tree Start Offset (TFO): VLQ-encoded integer, the absolute offset to the root node of the tree.

For each node in the tree:

  • Node Length: VLQ-encoded integer, total size of the node (including children for folder nodes).
  • Item Count: VLQ-encoded integer, number of child nodes (0 for content nodes).
  • Node Header Length: VLQ-encoded integer, length of the node's header section.
  • Node Metadata Length: VLQ-encoded integer, length of the metadata section.
  • Node Metadata Contents: Binary data of the specified length, containing node-specific metadata (e.g., ID3 tags or custom descriptors).
  • Reference Type: VLQ-encoded integer, 0x01 for inline data buffer or 0x02 for offset to another location in the file.
  • Contents: If reference type is 0x01, raw binary data (e.g., SMF MIDI data, DLS instruments, or WAV audio); if 0x02, a VLQ-encoded offset. Content nodes encapsulate resources, while folder nodes organize them hierarchically.

Additional format-wide properties:

  • Compression Support: Optional zlib compression (via UnPackerID in RP-040).
  • Metadata Support: MetaDataItems at any node, potentially including XML equivalents; ID3 tags (RP-047).
  • Tree Structure: Preorder traversal; root node is typically a folder containing sub-nodes for MIDI sequences and instruments.
  • File Extensions: .xmf (standard) or .mxmf (Mobile XMF variant).
  • MIME Type: audio/mobile-xmf for Mobile XMF.

These properties ensure the format's extensibility, compactness, and interoperability for music data distribution.

Two direct download links for files in .XMF format (specifically Mobile XMF variant, .mxmf extension, as these are the most commonly available examples):

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

The following is embeddable HTML with JavaScript code suitable for a Ghost blog post. It creates a drag-and-drop area where a user can drop a .XMF file. The script reads the file as binary, parses it according to the format (handling VLQ decoding), extracts all properties, and displays them on the screen in a structured text format.

Drag and drop a .XMF file here


4. Python Class for .XMF File Handling

The following Python class can open a .XMF file, decode and read its contents, print all properties to the console, and write a modified or new .XMF file (e.g., by reconstructing the structure).

import struct
import sys

class XMFParser:
    def __init__(self, filename=None):
        self.magic = None
        self.version = None
        self.file_type_id = None
        self.file_type_rev = None
        self.file_size = None
        self.mtt_length = None
        self.mtt_contents = None
        self.tree_start_offset = None
        self.nodes = []
        if filename:
            self.read(filename)

    def vlq_to_int(self, data, offset):
        value = 0
        while True:
            octet = data[offset]
            value = (value << 7) + (octet & 0x7F)
            offset += 1
            if (octet & 0x80) == 0:
                break
        return value, offset

    def read(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        offset = 0
        self.magic = data[offset:offset+4].decode('ascii')
        offset += 4
        self.version = data[offset:offset+4].decode('ascii')
        offset += 4
        if self.version == '2.00':
            self.file_type_id = struct.unpack_from('>i', data, offset)[0]
            offset += 4
            self.file_type_rev = struct.unpack_from('>i', data, offset)[0]
            offset += 4
        self.file_size, offset = self.vlq_to_int(data, offset)
        self.mtt_length, offset = self.vlq_to_int(data, offset)
        self.mtt_contents = data[offset:offset + self.mtt_length]
        offset += self.mtt_length
        self.tree_start_offset, offset = self.vlq_to_int(data, offset)
        self.nodes = self.parse_node(data, self.tree_start_offset)

    def parse_node(self, data, offset, depth=0):
        node_start = offset
        node_length, offset = self.vlq_to_int(data, offset)
        item_count, offset = self.vlq_to_int(data, offset)
        header_length, offset = self.vlq_to_int(data, offset)
        metadata_length, offset = self.vlq_to_int(data, offset)
        metadata_contents = data[offset:offset + metadata_length]
        offset += metadata_length
        ref_type, offset = self.vlq_to_int(data, offset)
        contents = None
        children = []
        if item_count > 0:
            contents = b'Folder node'
            for _ in range(item_count):
                child, offset = self.parse_node(data, offset, depth + 1)
                children.append(child)
        else:
            if ref_type == 1:
                content_size = node_length - (offset - node_start)
                contents = data[offset:offset + content_size]
                offset += content_size
            elif ref_type == 2:
                content_offset, offset = self.vlq_to_int(data, offset)
                contents = f'Offset: {content_offset}'.encode()
        return {
            'length': node_length,
            'item_count': item_count,
            'header_length': header_length,
            'metadata_length': metadata_length,
            'metadata_contents': metadata_contents,
            'ref_type': ref_type,
            'contents': contents,
            'children': children
        }, offset

    def print_properties(self):
        print(f"Magic Number: {self.magic}")
        print(f"Version: {self.version}")
        print(f"File Type ID: {self.file_type_id if self.file_type_id else 'N/A'}")
        print(f"File Type Revision ID: {self.file_type_rev if self.file_type_rev else 'N/A'}")
        print(f"File Size: {self.file_size}")
        print(f"MTT Length: {self.mtt_length}")
        print(f"MTT Contents: [Binary, {self.mtt_length} bytes]")
        print(f"Tree Start Offset: {self.tree_start_offset}")
        print("Nodes:")
        self.print_node(self.nodes, 0)

    def print_node(self, node, depth):
        indent = '  ' * depth
        print(f"{indent}Node Length: {node['length']}")
        print(f"{indent}Item Count: {node['item_count']}")
        print(f"{indent}Header Length: {node['header_length']}")
        print(f"{indent}Metadata Length: {node['metadata_length']}")
        print(f"{indent}Metadata Contents: [Binary, {node['metadata_length']} bytes]")
        print(f"{indent}Reference Type: {node['ref_type']}")
        print(f"{indent}Contents: {node['contents'] if isinstance(node['contents'], str) else f'[Binary, {len(node["contents"])} bytes]'}")
        for child in node['children']:
            self.print_node(child, depth + 1)

    def int_to_vlq(self, value):
        bytes_list = []
        while True:
            bytes_list.append(value & 0x7F)
            value >>= 7
            if value == 0:
                break
        bytes_list.reverse()
        for i in range(len(bytes_list) - 1):
            bytes_list[i] |= 0x80
        return bytes(bytes_list)

    def write_node(self, node):
        data = b''
        data += self.int_to_vlq(node['length'])
        data += self.int_to_vlq(node['item_count'])
        data += self.int_to_vlq(node['header_length'])
        data += self.int_to_vlq(node['metadata_length'])
        data += node['metadata_contents']
        data += self.int_to_vlq(node['ref_type'])
        if node['contents']:
            data += node['contents'] if isinstance(node['contents'], bytes) else node['contents'].encode()
        for child in node['children']:
            data += self.write_node(child)
        return data

    def write(self, filename):
        data = self.magic.encode() + self.version.encode()
        if self.version == '2.00':
            data += struct.pack('>i', self.file_type_id)
            data += struct.pack('>i', self.file_type_rev)
        data += self.int_to_vlq(self.file_size)
        data += self.int_to_vlq(self.mtt_length)
        data += self.mtt_contents
        data += self.int_to_vlq(self.tree_start_offset)
        data += self.write_node(self.nodes)
        with open(filename, 'wb') as f:
            f.write(data)

# Example usage:
# parser = XMFParser('example.xmf')
# parser.print_properties()
# parser.write('output.xmf')

To arrive at the solution for closed-ended mathematics questions: Not applicable, as this is not a mathematics question.

5. Java Class for .XMF File Handling

The following Java class performs similar operations: opening, decoding, reading, printing properties, and writing .XMF files.

import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class XMFParser {
    private String magic;
    private String version;
    private Integer fileTypeId;
    private Integer fileTypeRev;
    private long fileSize;
    private long mttLength;
    private byte[] mttContents;
    private long treeStartOffset;
    private Map<String, Object> rootNode;

    public XMFParser(String filename) throws IOException {
        read(filename);
    }

    private long vlqToInt(byte[] data, int[] offset) {
        long value = 0;
        while (true) {
            int octet = data[offset[0]] & 0xFF;
            value = (value << 7) + (octet & 0x7F);
            offset[0]++;
            if ((octet & 0x80) == 0) break;
        }
        return value;
    }

    private void read(String filename) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filename));
        int offset = 0;
        magic = new String(data, offset, 4, "ASCII");
        offset += 4;
        version = new String(data, offset, 4, "ASCII");
        offset += 4;
        int[] offArr = {offset};
        if (version.equals("2.00")) {
            fileTypeId = ByteBuffer.wrap(data, offset, 4).order(ByteOrder.BIG_ENDIAN).getInt();
            offset += 4;
            fileTypeRev = ByteBuffer.wrap(data, offset, 4).order(ByteOrder.BIG_ENDIAN).getInt();
            offset += 4;
            offArr[0] = offset;
        }
        fileSize = vlqToInt(data, offArr);
        offset = offArr[0];
        mttLength = vlqToInt(data, offArr);
        offset = offArr[0];
        mttContents = Arrays.copyOfRange(data, offset, offset + (int)mttLength);
        offset += mttLength;
        offArr[0] = offset;
        treeStartOffset = vlqToInt(data, offArr);
        rootNode = parseNode(data, (int)treeStartOffset, new int[]{(int)treeStartOffset})[0];
    }

    private Map<String, Object>[] parseNode(byte[] data, int offset, int[] offArr) {
        long nodeLength = vlqToInt(data, offArr);
        int nodeStart = offset;
        offset = offArr[0];
        long itemCount = vlqToInt(data, offArr);
        offset = offArr[0];
        long headerLength = vlqToInt(data, offArr);
        offset = offArr[0];
        long metadataLength = vlqToInt(data, offArr);
        offset = offArr[0];
        byte[] metadataContents = Arrays.copyOfRange(data, offset, offset + (int)metadataLength);
        offset += metadataLength;
        offArr[0] = offset;
        long refType = vlqToInt(data, offArr);
        offset = offArr[0];
        byte[] contents = null;
        List<Map<String, Object>> children = new ArrayList<>();
        if (itemCount > 0) {
            contents = "Folder node".getBytes();
            for (int i = 0; i < itemCount; i++) {
                Map<String, Object>[] childRes = parseNode(data, offset, offArr);
                children.add(childRes[0]);
                offset = offArr[0];
            }
        } else {
            if (refType == 1) {
                int contentSize = (int)(nodeLength - (offset - nodeStart));
                contents = Arrays.copyOfRange(data, offset, offset + contentSize);
                offset += contentSize;
            } else if (refType == 2) {
                long contentOffset = vlqToInt(data, offArr);
                contents = ("Offset: " + contentOffset).getBytes();
                offset = offArr[0];
            }
        }
        offArr[0] = nodeStart + (int)nodeLength;
        Map<String, Object> node = new HashMap<>();
        node.put("length", nodeLength);
        node.put("item_count", itemCount);
        node.put("header_length", headerLength);
        node.put("metadata_length", metadataLength);
        node.put("metadata_contents", metadataContents);
        node.put("ref_type", refType);
        node.put("contents", contents);
        node.put("children", children);
        return new Map[]{node};
    }

    public void printProperties() {
        System.out.println("Magic Number: " + magic);
        System.out.println("Version: " + version);
        System.out.println("File Type ID: " + (fileTypeId != null ? fileTypeId : "N/A"));
        System.out.println("File Type Revision ID: " + (fileTypeRev != null ? fileTypeRev : "N/A"));
        System.out.println("File Size: " + fileSize);
        System.out.println("MTT Length: " + mttLength);
        System.out.println("MTT Contents: [Binary, " + mttLength + " bytes]");
        System.out.println("Tree Start Offset: " + treeStartOffset);
        System.out.println("Root Node:");
        printNode(rootNode, 0);
    }

    private void printNode(Map<String, Object> node, int depth) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + "Node Length: " + node.get("length"));
        System.out.println(indent + "Item Count: " + node.get("item_count"));
        System.out.println(indent + "Header Length: " + node.get("header_length"));
        System.out.println(indent + "Metadata Length: " + node.get("metadata_length"));
        System.out.println(indent + "Metadata Contents: [Binary, " + node.get("metadata_length") + " bytes]");
        System.out.println(indent + "Reference Type: " + node.get("ref_type"));
        byte[] contents = (byte[]) node.get("contents");
        System.out.println(indent + "Contents: " + (contents != null ? new String(contents) : "[Binary data]"));
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> children = (List<Map<String, Object>>) node.get("children");
        for (Map<String, Object> child : children) {
            printNode(child, depth + 1);
        }
    }

    private byte[] intToVlq(long value) {
        List<Byte> bytesList = new ArrayList<>();
        while (true) {
            bytesList.add((byte) (value & 0x7F));
            value >>= 7;
            if (value == 0) break;
        }
        Collections.reverse(bytesList);
        for (int i = 0; i < bytesList.size() - 1; i++) {
            bytesList.set(i, (byte) (bytesList.get(i) | 0x80));
        }
        byte[] vlq = new byte[bytesList.size()];
        for (int i = 0; i < vlq.length; i++) {
            vlq[i] = bytesList.get(i);
        }
        return vlq;
    }

    private byte[] writeNode(Map<String, Object> node) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(intToVlq((long) node.get("length")));
        baos.write(intToVlq((long) node.get("item_count")));
        baos.write(intToVlq((long) node.get("header_length")));
        baos.write(intToVlq((long) node.get("metadata_length")));
        baos.write((byte[]) node.get("metadata_contents"));
        baos.write(intToVlq((long) node.get("ref_type")));
        byte[] contents = (byte[]) node.get("contents");
        if (contents != null) baos.write(contents);
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> children = (List<Map<String, Object>>) node.get("children");
        for (Map<String, Object> child : children) {
            baos.write(writeNode(child));
        }
        return baos.toByteArray();
    }

    public void write(String filename) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(magic.getBytes("ASCII"));
        baos.write(version.getBytes("ASCII"));
        if (version.equals("2.00")) {
            baos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(fileTypeId).array());
            baos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(fileTypeRev).array());
        }
        baos.write(intToVlq(fileSize));
        baos.write(intToVlq(mttLength));
        baos.write(mttContents);
        baos.write(intToVlq(treeStartOffset));
        baos.write(writeNode(rootNode));
        Files.write(Paths.get(filename), baos.toByteArray());
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     XMFParser parser = new XMFParser("example.xmf");
    //     parser.printProperties();
    //     parser.write("output.xmf");
    // }
}

6. JavaScript Class for .XMF File Handling

The following JavaScript class can be used in a Node.js environment (requires 'fs' module) to open, decode, read, print properties to console, and write .XMF files.

const fs = require('fs');

class XMFParser {
  constructor(filename) {
    if (filename) this.read(filename);
  }

  vlqToInt(data, offset) {
    let value = 0;
    while (true) {
      const octet = data[offset];
      value = (value << 7) + (octet & 0x7F);
      offset++;
      if ((octet & 0x80) === 0) break;
    }
    return { value, offset };
  }

  read(filename) {
    const data = fs.readFileSync(filename);
    let offset = 0;
    this.magic = data.toString('ascii', offset, offset + 4);
    offset += 4;
    this.version = data.toString('ascii', offset, offset + 4);
    offset += 4;
    if (this.version === '2.00') {
      this.fileTypeId = data.readInt32BE(offset);
      offset += 4;
      this.fileTypeRev = data.readInt32BE(offset);
      offset += 4;
    }
    let res = this.vlqToInt(data, offset);
    this.fileSize = res.value;
    offset = res.offset;
    res = this.vlqToInt(data, offset);
    this.mttLength = res.value;
    offset = res.offset;
    this.mttContents = data.slice(offset, offset + this.mttLength);
    offset += this.mttLength;
    res = this.vlqToInt(data, offset);
    this.treeStartOffset = res.value;
    offset = res.offset;
    this.rootNode = this.parseNode(data, this.treeStartOffset);
  }

  parseNode(data, offset) {
    const nodeStart = offset;
    let res = this.vlqToInt(data, offset);
    const nodeLength = res.value;
    offset = res.offset;
    res = this.vlqToInt(data, offset);
    const itemCount = res.value;
    offset = res.offset;
    res = this.vlqToInt(data, offset);
    const headerLength = res.value;
    offset = res.offset;
    res = this.vlqToInt(data, offset);
    const metadataLength = res.value;
    offset = res.offset;
    const metadataContents = data.slice(offset, offset + metadataLength);
    offset += metadataLength;
    res = this.vlqToInt(data, offset);
    const refType = res.value;
    offset = res.offset;
    let contents = null;
    const children = [];
    if (itemCount > 0) {
      contents = 'Folder node';
      for (let i = 0; i < itemCount; i++) {
        const child = this.parseNode(data, offset);
        children.push(child);
        offset = child.nextOffset;
      }
    } else {
      if (refType === 1) {
        const contentSize = nodeLength - (offset - nodeStart);
        contents = data.slice(offset, offset + contentSize);
        offset += contentSize;
      } else if (refType === 2) {
        res = this.vlqToInt(data, offset);
        contents = `Offset: ${res.value}`;
        offset = res.offset;
      }
    }
    return {
      length: nodeLength,
      itemCount,
      headerLength,
      metadataLength,
      metadataContents,
      refType,
      contents,
      children,
      nextOffset: nodeStart + nodeLength
    };
  }

  printProperties() {
    console.log(`Magic Number: ${this.magic}`);
    console.log(`Version: ${this.version}`);
    console.log(`File Type ID: ${this.fileTypeId || 'N/A'}`);
    console.log(`File Type Revision ID: ${this.fileTypeRev || 'N/A'}`);
    console.log(`File Size: ${this.fileSize}`);
    console.log(`MTT Length: ${this.mttLength}`);
    console.log(`MTT Contents: [Binary, ${this.mttLength} bytes]`);
    console.log(`Tree Start Offset: ${this.treeStartOffset}`);
    console.log('Root Node:');
    this.printNode(this.rootNode, 0);
  }

  printNode(node, depth) {
    const indent = '  '.repeat(depth);
    console.log(`${indent}Node Length: ${node.length}`);
    console.log(`${indent}Item Count: ${node.itemCount}`);
    console.log(`${indent}Header Length: ${node.headerLength}`);
    console.log(`${indent}Metadata Length: ${node.metadataLength}`);
    console.log(`${indent}Metadata Contents: [Binary, ${node.metadataLength} bytes]`);
    console.log(`${indent}Reference Type: ${node.refType}`);
    console.log(`${indent}Contents: ${node.contents instanceof Buffer ? `[Binary, ${node.contents.length} bytes]` : node.contents}`);
    node.children.forEach(child => this.printNode(child, depth + 1));
  }

  intToVlq(value) {
    const bytesList = [];
    while (true) {
      bytesList.push(value & 0x7F);
      value = Math.floor(value / 128);
      if (value === 0) break;
    }
    bytesList.reverse();
    for (let i = 0; i < bytesList.length - 1; i++) {
      bytesList[i] |= 0x80;
    }
    return Buffer.from(bytesList);
  }

  writeNode(node) {
    let data = Buffer.alloc(0);
    data = Buffer.concat([data, this.intToVlq(node.length)]);
    data = Buffer.concat([data, this.intToVlq(node.itemCount)]);
    data = Buffer.concat([data, this.intToVlq(node.headerLength)]);
    data = Buffer.concat([data, this.intToVlq(node.metadataLength)]);
    data = Buffer.concat([data, node.metadataContents]);
    data = Buffer.concat([data, this.intToVlq(node.refType)]);
    if (node.contents) {
      data = Buffer.concat([data, typeof node.contents === 'string' ? Buffer.from(node.contents) : node.contents]);
    }
    for (const child of node.children) {
      data = Buffer.concat([data, this.writeNode(child)]);
    }
    return data;
  }

  write(filename) {
    let data = Buffer.from(this.magic, 'ascii');
    data = Buffer.concat([data, Buffer.from(this.version, 'ascii')]);
    if (this.version === '2.00') {
      const idBuf = Buffer.alloc(4);
      idBuf.writeInt32BE(this.fileTypeId, 0);
      data = Buffer.concat([data, idBuf]);
      const revBuf = Buffer.alloc(4);
      revBuf.writeInt32BE(this.fileTypeRev, 0);
      data = Buffer.concat([data, revBuf]);
    }
    data = Buffer.concat([data, this.intToVlq(this.fileSize)]);
    data = Buffer.concat([data, this.intToVlq(this.mttLength)]);
    data = Buffer.concat([data, this.mttContents]);
    data = Buffer.concat([data, this.intToVlq(this.treeStartOffset)]);
    data = Buffer.concat([data, this.writeNode(this.rootNode)]);
    fs.writeFileSync(filename, data);
  }
}

// Example usage:
// const parser = new XMFParser('example.xmf');
// parser.printProperties();
// parser.write('output.xmf');

7. C++ Class for .XMF File Handling

The following C++ class handles opening, decoding, reading, printing to console, and writing .XMF files. Compile with a C++11 or later compiler.

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

struct XMFNode {
    uint64_t length;
    uint64_t item_count;
    uint64_t header_length;
    uint64_t metadata_length;
    std::vector<uint8_t> metadata_contents;
    uint64_t ref_type;
    std::vector<uint8_t> contents;
    std::vector<XMFNode> children;
};

class XMFParser {
private:
    std::string magic;
    std::string version;
    int32_t file_type_id = 0;
    int32_t file_type_rev = 0;
    uint64_t file_size;
    uint64_t mtt_length;
    std::vector<uint8_t> mtt_contents;
    uint64_t tree_start_offset;
    XMFNode root_node;

    uint64_t vlq_to_int(const std::vector<uint8_t>& data, size_t& offset) {
        uint64_t value = 0;
        while (true) {
            uint8_t octet = data[offset];
            value = (value << 7) + (octet & 0x7F);
            offset++;
            if ((octet & 0x80) == 0) break;
        }
        return value;
    }

    XMFNode parse_node(const std::vector<uint8_t>& data, size_t& offset) {
        size_t node_start = offset;
        XMFNode node;
        node.length = vlq_to_int(data, offset);
        node.item_count = vlq_to_int(data, offset);
        node.header_length = vlq_to_int(data, offset);
        node.metadata_length = vlq_to_int(data, offset);
        node.metadata_contents.assign(data.begin() + offset, data.begin() + offset + node.metadata_length);
        offset += node.metadata_length;
        node.ref_type = vlq_to_int(data, offset);
        if (node.item_count > 0) {
            node.contents = std::vector<uint8_t>{'F', 'o', 'l', 'd', 'e', 'r', ' ', 'n', 'o', 'd', 'e'};
            for (uint64_t i = 0; i < node.item_count; i++) {
                node.children.push_back(parse_node(data, offset));
            }
        } else {
            if (node.ref_type == 1) {
                size_t content_size = node.length - (offset - node_start);
                node.contents.assign(data.begin() + offset, data.begin() + offset + content_size);
                offset += content_size;
            } else if (node.ref_type == 2) {
                uint64_t content_offset = vlq_to_int(data, offset);
                std::string off_str = "Offset: " + std::to_string(content_offset);
                node.contents.assign(off_str.begin(), off_str.end());
            }
        }
        return node;
    }

    void print_node(const XMFNode& node, int depth) {
        std::string indent(depth * 2, ' ');
        std::cout << indent << "Node Length: " << node.length << std::endl;
        std::cout << indent << "Item Count: " << node.item_count << std::endl;
        std::cout << indent << "Header Length: " << node.header_length << std::endl;
        std::cout << indent << "Metadata Length: " << node.metadata_length << std::endl;
        std::cout << indent << "Metadata Contents: [Binary, " << node.metadata_length << " bytes]" << std::endl;
        std::cout << indent << "Reference Type: " << node.ref_type << std::endl;
        std::string contents_str(node.contents.begin(), node.contents.end());
        std::cout << indent << "Contents: " << (node.contents.size() > 20 ? "[Binary, " + std::to_string(node.contents.size()) + " bytes]" : contents_str) << std::endl;
        for (const auto& child : node.children) {
            print_node(child, depth + 1);
        }
    }

    std::vector<uint8_t> int_to_vlq(uint64_t value) {
        std::vector<uint8_t> bytes_list;
        while (true) {
            bytes_list.push_back(value & 0x7F);
            value >>= 7;
            if (value == 0) break;
        }
        std::reverse(bytes_list.begin(), bytes_list.end());
        for (size_t i = 0; i < bytes_list.size() - 1; i++) {
            bytes_list[i] |= 0x80;
        }
        return bytes_list;
    }

    std::vector<uint8_t> write_node(const XMFNode& node) {
        std::vector<uint8_t> data;
        auto append = [&data](const std::vector<uint8_t>& v) { data.insert(data.end(), v.begin(), v.end()); };
        append(int_to_vlq(node.length));
        append(int_to_vlq(node.item_count));
        append(int_to_vlq(node.header_length));
        append(int_to_vlq(node.metadata_length));
        append(node.metadata_contents);
        append(int_to_vlq(node.ref_type));
        data.insert(data.end(), node.contents.begin(), node.contents.end());
        for (const auto& child : node.children) {
            append(write_node(child));
        }
        return data;
    }

public:
    void read(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        std::vector<uint8_t> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        size_t offset = 0;
        magic = std::string(data.begin() + offset, data.begin() + offset + 4);
        offset += 4;
        version = std::string(data.begin() + offset, data.begin() + offset + 4);
        offset += 4;
        if (version == "2.00") {
            std::memcpy(&file_type_id, data.data() + offset, 4);
            file_type_id = __builtin_bswap32(file_type_id);  // Assume big-endian
            offset += 4;
            std::memcpy(&file_type_rev, data.data() + offset, 4);
            file_type_rev = __builtin_bswap32(file_type_rev);
            offset += 4;
        }
        file_size = vlq_to_int(data, offset);
        mtt_length = vlq_to_int(data, offset);
        mtt_contents.assign(data.begin() + offset, data.begin() + offset + mtt_length);
        offset += mtt_length;
        tree_start_offset = vlq_to_int(data, offset);
        root_node = parse_node(data, offset = tree_start_offset);
    }

    void print_properties() {
        std::cout << "Magic Number: " << magic << std::endl;
        std::cout << "Version: " << version << std::endl;
        std::cout << "File Type ID: " << (version == "2.00" ? std::to_string(file_type_id) : "N/A") << std::endl;
        std::cout << "File Type Revision ID: " << (version == "2.00" ? std::to_string(file_type_rev) : "N/A") << std::endl;
        std::cout << "File Size: " << file_size << std::endl;
        std::cout << "MTT Length: " << mtt_length << std::endl;
        std::cout << "MTT Contents: [Binary, " << mtt_length << " bytes]" << std::endl;
        std::cout << "Tree Start Offset: " << tree_start_offset << std::endl;
        std::cout << "Root Node:" << std::endl;
        print_node(root_node, 0);
    }

    void write(const std::string& filename) {
        std::vector<uint8_t> data(magic.begin(), magic.end());
        data.insert(data.end(), version.begin(), version.end());
        if (version == "2.00") {
            int32_t be_id = __builtin_bswap32(file_type_id);
            data.insert(data.end(), reinterpret_cast<uint8_t*>(&be_id), reinterpret_cast<uint8_t*>(&be_id) + 4);
            int32_t be_rev = __builtin_bswap32(file_type_rev);
            data.insert(data.end(), reinterpret_cast<uint8_t*>(&be_rev), reinterpret_cast<uint8_t*>(&be_rev) + 4);
        }
        auto append = [&data](const std::vector<uint8_t>& v) { data.insert(data.end(), v.begin(), v.end()); };
        append(int_to_vlq(file_size));
        append(int_to_vlq(mtt_length));
        append(mtt_contents);
        append(int_to_vlq(tree_start_offset));
        append(write_node(root_node));
        std::ofstream file(filename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(data.data()), data.size());
    }
};

// Example usage:
// int main() {
//     XMFParser parser;
//     parser.read("example.xmf");
//     parser.print_properties();
//     parser.write("output.xmf");
//     return 0;
// }