Task 403: .MNT File Format

Task 403: .MNT File Format

The .MNT file format is the Visual FoxPro Menu Memo format, a binary format used to store memo (variable-length) data associated with menu structures in Visual FoxPro applications. It is structurally identical to the FoxPro .FPT memo file format.

  1. The properties of this file format intrinsic to its file system are:
  • Next Free Block Number (4 bytes, big-endian unsigned integer at offset 0)
  • Unused (2 bytes at offset 4)
  • Block Size (2 bytes, big-endian unsigned integer at offset 6)
  • Reserved Area (504 bytes at offset 8)
  • Memo Blocks (variable number, starting at offset 512, each block is of fixed size specified by the block size property, and consists of:
  • Type (4 bytes, big-endian unsigned integer; typically 0 for picture/binary or 1 for text/memo)
  • Length (4 bytes, big-endian unsigned integer; the size of the data payload)
  • Data (variable bytes equal to the length value; the actual memo content)
  • Padding (remaining bytes to fill the full block size; usually zeros or garbage)

I was unable to find direct download links for .MNT files after searching available sources. No valid examples were located.

Here is the HTML with embedded JavaScript for a simple page that allows drag-and-drop of a .MNT file and dumps the properties to the screen:

MNT File Dumper
Drag and drop a .MNT file here
  1. Here is the Python class:
import struct

class MNTFile:
    def __init__(self, filename):
        self.filename = filename
        self.next_free_block = 0
        self.unused = 0
        self.block_size = 0
        self.reserved = b''
        self.memo_blocks = []  # list of dicts: {'type': int, 'length': int, 'data': bytes}

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        if len(data) < 512:
            raise ValueError("Invalid .MNT file")
        self.next_free_block = struct.unpack('>I', data[0:4])[0]
        self.unused = struct.unpack('>H', data[4:6])[0]
        self.block_size = struct.unpack('>H', data[6:8])[0]
        self.reserved = data[8:512]
        offset = 512
        block_number = 1
        while offset + self.block_size <= len(data):
            type_ = struct.unpack('>I', data[offset:offset+4])[0]
            length = struct.unpack('>I', data[offset+4:offset+8])[0]
            if length > 0 and (type_ == 0 or type_ == 1):
                end = offset + 8 + length
                if end > len(data):
                    break
                memo_data = data[offset+8:end]
                self.memo_blocks.append({'type': type_, 'length': length, 'data': memo_data})
            offset += self.block_size
            block_number += 1

    def print_properties(self):
        print("Next Free Block Number:", self.next_free_block)
        print("Unused:", self.unused)
        print("Block Size:", self.block_size)
        print("Reserved Area: (504 bytes, hex):", self.reserved.hex()[:100] + "...")  # Truncated for brevity
        for idx, block in enumerate(self.memo_blocks, 1):
            print(f"Memo Block {idx}:")
            print("  Type:", block['type'])
            print("  Length:", block['length'])
            print("  Data (first 100 bytes as string):", block['data'][:100].decode('utf-8', errors='ignore') + "...")

    def write(self, new_filename=None):
        if new_filename is None:
            new_filename = self.filename
        header = struct.pack('>I', self.next_free_block) + struct.pack('>H', self.unused) + struct.pack('>H', self.block_size) + self.reserved
        body = b''
        for block in self.memo_blocks:
            block_header = struct.pack('>I', block['type']) + struct.pack('>I', block['length'])
            padding_size = self.block_size - (8 + block['length'])
            body += block_header + block['data'] + b'\x00' * padding_size
        with open(new_filename, 'wb') as f:
            f.write(header + b'\x00' * (512 - len(header)) + body)  # Ensure header is 512 bytes

# Example usage:
# mnt = MNTFile('example.mnt')
# mnt.read()
# mnt.print_properties()
# mnt.write('modified.mnt')
  1. Here is the Java class:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class MNTFile {
    private String filename;
    private int nextFreeBlock;
    private short unused;
    private short blockSize;
    private byte[] reserved = new byte[504];
    private java.util.List<MemoBlock> memoBlocks = new java.util.ArrayList<>();

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

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            byte[] data = fis.readAllBytes();
            if (data.length < 512) {
                throw new IOException("Invalid .MNT file");
            }
            ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
            nextFreeBlock = bb.getInt(0);
            unused = bb.getShort(4);
            blockSize = bb.getShort(6);
            bb.position(8);
            bb.get(reserved);
            int offset = 512;
            int blockNumber = 1;
            while (offset + blockSize <= data.length) {
                int type = bb.getInt(offset);
                int length = bb.getInt(offset + 4);
                if (length > 0 && (type == 0 || type == 1)) {
                    byte[] memoData = new byte[length];
                    bb.position(offset + 8);
                    bb.get(memoData);
                    memoBlocks.add(new MemoBlock(type, length, memoData));
                }
                offset += blockSize;
                blockNumber++;
            }
        }
    }

    public void printProperties() {
        System.out.println("Next Free Block Number: " + nextFreeBlock);
        System.out.println("Unused: " + unused);
        System.out.println("Block Size: " + blockSize);
        System.out.print("Reserved Area (first 100 bytes hex): ");
        for (int i = 0; i < Math.min(100, reserved.length); i++) {
            System.out.print(String.format("%02X ", reserved[i]));
        }
        System.out.println("...");
        for (int i = 0; i < memoBlocks.size(); i++) {
            MemoBlock block = memoBlocks.get(i);
            System.out.println("Memo Block " + (i + 1) + ":");
            System.out.println("  Type: " + block.type);
            System.out.println("  Length: " + block.length);
            System.out.print("  Data (first 100 bytes as string): ");
            System.out.println(new String(block.data, 0, Math.min(100, block.length)));
        }
    }

    public void write(String newFilename) throws IOException {
        if (newFilename == null) {
            newFilename = filename;
        }
        ByteBuffer bb = ByteBuffer.allocate(512).order(ByteOrder.BIG_ENDIAN);
        bb.putInt(nextFreeBlock);
        bb.putShort(unused);
        bb.putShort(blockSize);
        bb.put(reserved);
        byte[] header = bb.array();  // 512 bytes
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            fos.write(header);
            for (MemoBlock block : memoBlocks) {
                ByteBuffer blockBb = ByteBuffer.allocate(blockSize).order(ByteOrder.BIG_ENDIAN);
                blockBb.putInt(block.type);
                blockBb.putInt(block.length);
                blockBb.put(block.data);
                // Padding added automatically as zeros
                fos.write(blockBb.array());
            }
        }
    }

    private static class MemoBlock {
        int type;
        int length;
        byte[] data;

        MemoBlock(int type, int length, byte[] data) {
            this.type = type;
            this.length = length;
            this.data = data;
        }
    }

    // Example usage:
    // MNTFile mnt = new MNTFile("example.mnt");
    // mnt.read();
    // mnt.printProperties();
    // mnt.write("modified.mnt");
}
  1. Here is the JavaScript class (for Node.js, using fs module for file I/O):
const fs = require('fs');

class MNTFile {
  constructor(filename) {
    this.filename = filename;
    this.nextFreeBlock = 0;
    this.unused = 0;
    this.blockSize = 0;
    this.reserved = Buffer.alloc(504);
    this.memoBlocks = []; // array of {type: number, length: number, data: Buffer}
  }

  read() {
    const data = fs.readFileSync(this.filename);
    if (data.length < 512) {
      throw new Error('Invalid .MNT file');
    }
    this.nextFreeBlock = data.readUInt32BE(0);
    this.unused = data.readUInt16BE(4);
    this.blockSize = data.readUInt16BE(6);
    data.copy(this.reserved, 0, 8, 512);
    let offset = 512;
    let blockNumber = 1;
    while (offset + this.blockSize <= data.length) {
      const type = data.readUInt32BE(offset);
      const length = data.readUInt32BE(offset + 4);
      if (length > 0 && (type === 0 || type === 1)) {
        const memoData = Buffer.alloc(length);
        data.copy(memoData, 0, offset + 8, offset + 8 + length);
        this.memoBlocks.push({type, length, data: memoData});
      }
      offset += this.blockSize;
      blockNumber++;
    }
  }

  printProperties() {
    console.log('Next Free Block Number:', this.nextFreeBlock);
    console.log('Unused:', this.unused);
    console.log('Block Size:', this.blockSize);
    console.log('Reserved Area (first 100 bytes hex):', this.reserved.slice(0, 100).toString('hex'));
    this.memoBlocks.forEach((block, index) => {
      console.log(`Memo Block ${index + 1}:`);
      console.log('  Type:', block.type);
      console.log('  Length:', block.length);
      console.log('  Data (first 100 bytes as string):', block.data.slice(0, 100).toString('utf8'));
    });
  }

  write(newFilename = this.filename) {
    let buffer = Buffer.alloc(512);
    buffer.writeUInt32BE(this.nextFreeBlock, 0);
    buffer.writeUInt16BE(this.unused, 4);
    buffer.writeUInt16BE(this.blockSize, 6);
    this.reserved.copy(buffer, 8);
    this.memoBlocks.forEach(block => {
      let blockBuffer = Buffer.alloc(this.blockSize);
      blockBuffer.writeUInt32BE(block.type, 0);
      blockBuffer.writeUInt32BE(block.length, 4);
      block.data.copy(blockBuffer, 8);
      // Padding is zeros by default
      buffer = Buffer.concat([buffer, blockBuffer]);
    });
    fs.writeFileSync(newFilename, buffer);
  }
}

// Example usage:
// const mnt = new MNTFile('example.mnt');
// mnt.read();
// mnt.printProperties();
// mnt.write('modified.mnt');
  1. Here is the C "class" (using struct with functions, as C does not have classes):
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h> // For big-endian conversions, assume available or use custom

typedef struct {
  uint32_t type;
  uint32_t length;
  uint8_t *data;
} MemoBlock;

typedef struct {
  char *filename;
  uint32_t next_free_block;
  uint16_t unused;
  uint16_t block_size;
  uint8_t reserved[504];
  MemoBlock *memo_blocks;
  size_t num_memo_blocks;
} MNTFile;

MNTFile *mnt_create(const char *filename) {
  MNTFile *mnt = malloc(sizeof(MNTFile));
  mnt->filename = strdup(filename);
  mnt->next_free_block = 0;
  mnt->unused = 0;
  mnt->block_size = 0;
  memset(mnt->reserved, 0, 504);
  mnt->memo_blocks = NULL;
  mnt->num_memo_blocks = 0;
  return mnt;
}

void mnt_destroy(MNTFile *mnt) {
  for (size_t i = 0; i < mnt->num_memo_blocks; i++) {
    free(mnt->memo_blocks[i].data);
  }
  free(mnt->memo_blocks);
  free(mnt->filename);
  free(mnt);
}

int mnt_read(MNTFile *mnt) {
  FILE *f = fopen(mnt->filename, "rb");
  if (!f) return 1;
  fseek(f, 0, SEEK_END);
  long file_size = ftell(f);
  fseek(f, 0, SEEK_SET);
  uint8_t *data = malloc(file_size);
  fread(data, 1, file_size, f);
  fclose(f);
  if (file_size < 512) {
    free(data);
    return 1;
  }
  mnt->next_free_block = be32toh(*(uint32_t*)(data + 0));
  mnt->unused = be16toh(*(uint16_t*)(data + 4));
  mnt->block_size = be16toh(*(uint16_t*)(data + 6));
  memcpy(mnt->reserved, data + 8, 504);
  long offset = 512;
  size_t block_number = 1;
  while (offset + mnt->block_size <= file_size) {
    uint32_t type = be32toh(*(uint32_t*)(data + offset));
    uint32_t length = be32toh(*(uint32_t*)(data + offset + 4));
    if (length > 0 && (type == 0 || type == 1)) {
      uint8_t *memo_data = malloc(length);
      memcpy(memo_data, data + offset + 8, length);
      mnt->memo_blocks = realloc(mnt->memo_blocks, sizeof(MemoBlock) * (mnt->num_memo_blocks + 1));
      mnt->memo_blocks[mnt->num_memo_blocks].type = type;
      mnt->memo_blocks[mnt->num_memo_blocks].length = length;
      mnt->memo_blocks[mnt->num_memo_blocks].data = memo_data;
      mnt->num_memo_blocks++;
    }
    offset += mnt->block_size;
    block_number++;
  }
  free(data);
  return 0;
}

void mnt_print_properties(const MNTFile *mnt) {
  printf("Next Free Block Number: %u\n", mnt->next_free_block);
  printf("Unused: %hu\n", mnt->unused);
  printf("Block Size: %hu\n", mnt->block_size);
  printf("Reserved Area (first 100 bytes hex): ");
  for (int i = 0; i < 100 && i < 504; i++) {
    printf("%02x ", mnt->reserved[i]);
  }
  printf("...\n");
  for (size_t i = 0; i < mnt->num_memo_blocks; i++) {
    MemoBlock block = mnt->memo_blocks[i];
    printf("Memo Block %zu:\n", i + 1);
    printf("  Type: %u\n", block.type);
    printf("  Length: %u\n", block.length);
    printf("  Data (first 100 bytes as string): ");
    for (int j = 0; j < 100 && j < block.length; j++) {
      putchar(block.data[j]);
    }
    printf("...\n");
  }
}

int mnt_write(const MNTFile *mnt, const char *new_filename) {
  if (!new_filename) new_filename = mnt->filename;
  FILE *f = fopen(new_filename, "wb");
  if (!f) return 1;
  uint8_t header[512] = {0};
  *(uint32_t*)(header + 0) = htobe32(mnt->next_free_block);
  *(uint16_t*)(header + 4) = htobe16(mnt->unused);
  *(uint16_t*)(header + 6) = htobe16(mnt->block_size);
  memcpy(header + 8, mnt->reserved, 504);
  fwrite(header, 1, 512, f);
  for (size_t i = 0; i < mnt->num_memo_blocks; i++) {
    MemoBlock block = mnt->memo_blocks[i];
    uint8_t *block_buf = calloc(1, mnt->block_size);
    *(uint32_t*)(block_buf + 0) = htobe32(block.type);
    *(uint32_t*)(block_buf + 4) = htobe32(block.length);
    memcpy(block_buf + 8, block.data, block.length);
    fwrite(block_buf, 1, mnt->block_size, f);
    free(block_buf);
  }
  fclose(f);
  return 0;
}

// Example usage:
// MNTFile *mnt = mnt_create("example.mnt");
// mnt_read(mnt);
// mnt_print_properties(mnt);
// mnt_write(mnt, "modified.mnt");
// mnt_destroy(mnt);