Task 284: .HDI File Format

Task 284: .HDI File Format

.HDI File Format Specifications

The .HDI file format is a hard disk image format primarily used for emulating NEC PC-98 systems. It consists of a header followed by an optional comment section and the raw disk data in cylinder/head/sector (CHS) order. The header is 32 bytes long at minimum, with all fields as 4-byte little-endian unsigned integers (DWORD). The format does not support variable sector sizes or missing sectors.

  1. List of all the properties of this file format intrinsic to its file system:
  • Reserved: 4 bytes at offset 0x00, must be 0.
  • FDDType: 4 bytes at offset 0x04, typically represents the disk capacity in MB for HDI files.
  • HeaderSize: 4 bytes at offset 0x08, size of the entire header in bytes (minimum 32, default often 4096).
  • DataSize: 4 bytes at offset 0x0C, size of the raw data segment in bytes.
  • BytesPerSector: 4 bytes at offset 0x10, number of bytes per sector (uniform across the disk).
  • Sectors: 4 bytes at offset 0x14, number of sectors per track (uniform).
  • Heads: 4 bytes at offset 0x18, number of heads (uniform).
  • Cylinders: 4 bytes at offset 0x1C, number of cylinders (uniform).
  • Comment: Variable length (HeaderSize - 32 bytes) starting at offset 0x20, optional text or data.
  1. Two direct download links for files of format .HDI:
  1. Ghost blog embedded HTML JavaScript for drag and drop .HDI file dump:
.HDI File Properties Dumper
Drag and drop .HDI file here

This HTML can be embedded in a Ghost blog post. It allows dragging and dropping a .HDI file, reads the header using JavaScript, and displays the properties on the screen.

  1. Python class for .HDI file handling:
import struct
import os

class HDIFile:
    def __init__(self, filepath=None):
        self.filepath = filepath
        self.reserved = 0
        self.fdd_type = 0
        self.header_size = 0
        self.data_size = 0
        self.bytes_per_sector = 0
        self.sectors = 0
        self.heads = 0
        self.cylinders = 0
        self.comment = b''
        if filepath:
            self.read(filepath)

    def read(self, filepath):
        self.filepath = filepath
        with open(filepath, 'rb') as f:
            header = f.read(32)
            if len(header) < 32:
                raise ValueError("Invalid HDI file: Header too short")
            (
                self.reserved,
                self.fdd_type,
                self.header_size,
                self.data_size,
                self.bytes_per_sector,
                self.sectors,
                self.heads,
                self.cylinders
            ) = struct.unpack('<8I', header)
            if self.reserved != 0:
                print("Warning: Reserved field is not zero")
            if self.header_size > 32:
                self.comment = f.read(self.header_size - 32)
            # Data follows, but not reading it fully here

    def print_properties(self):
        print(f"Reserved: {self.reserved}")
        print(f"FDDType: {self.fdd_type}")
        print(f"HeaderSize: {self.header_size}")
        print(f"DataSize: {self.data_size}")
        print(f"BytesPerSector: {self.bytes_per_sector}")
        print(f"Sectors: {self.sectors}")
        print(f"Heads: {self.heads}")
        print(f"Cylinders: {self.cylinders}")
        print(f"Comment: {self.comment.decode('utf-8', errors='ignore').strip() or 'None'}")

    def write(self, output_path=None):
        if not output_path:
            output_path = self.filepath or 'output.hdi'
        with open(output_path, 'wb') as f:
            header = struct.pack('<8I', 
                self.reserved, self.fdd_type, self.header_size, self.data_size,
                self.bytes_per_sector, self.sectors, self.heads, self.cylinders
            )
            f.write(header)
            f.write(self.comment)
            # Note: Data segment not handled here; append manually if needed

Example usage: hdi = HDIFile('example.hdi'); hdi.print_properties(); hdi.write('modified.hdi')

  1. Java class for .HDI file handling:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

public class HDIFile {
    private Path filepath;
    private long reserved;
    private long fddType;
    private long headerSize;
    private long dataSize;
    private long bytesPerSector;
    private long sectors;
    private long heads;
    private long cylinders;
    private byte[] comment = new byte[0];

    public HDIFile(Path filepath) throws IOException {
        this.filepath = filepath;
        read(filepath);
    }

    public HDIFile() {}

    public void read(Path filepath) throws IOException {
        this.filepath = filepath;
        try (FileChannel channel = FileChannel.open(filepath, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN);
            channel.read(buffer);
            buffer.flip();
            reserved = buffer.getInt() & 0xFFFFFFFFL;
            fddType = buffer.getInt() & 0xFFFFFFFFL;
            headerSize = buffer.getInt() & 0xFFFFFFFFL;
            dataSize = buffer.getInt() & 0xFFFFFFFFL;
            bytesPerSector = buffer.getInt() & 0xFFFFFFFFL;
            sectors = buffer.getInt() & 0xFFFFFFFFL;
            heads = buffer.getInt() & 0xFFFFFFFFL;
            cylinders = buffer.getInt() & 0xFFFFFFFFL;
            if (reserved != 0) {
                System.out.println("Warning: Reserved field is not zero");
            }
            if (headerSize > 32) {
                ByteBuffer commentBuffer = ByteBuffer.allocate((int)(headerSize - 32));
                channel.read(commentBuffer);
                comment = commentBuffer.array();
            }
        }
    }

    public void printProperties() {
        System.out.println("Reserved: " + reserved);
        System.out.println("FDDType: " + fddType);
        System.out.println("HeaderSize: " + headerSize);
        System.out.println("DataSize: " + dataSize);
        System.out.println("BytesPerSector: " + bytesPerSector);
        System.out.println("Sectors: " + sectors);
        System.out.println("Heads: " + heads);
        System.out.println("Cylinders: " + cylinders);
        String commentStr = new String(comment).trim();
        System.out.println("Comment: " + (commentStr.isEmpty() ? "None" : commentStr));
    }

    public void write(Path outputPath) throws IOException {
        if (outputPath == null) {
            outputPath = filepath != null ? filepath : Paths.get("output.hdi");
        }
        try (FileChannel channel = FileChannel.open(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            ByteBuffer buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN);
            buffer.putInt((int) reserved);
            buffer.putInt((int) fddType);
            buffer.putInt((int) headerSize);
            buffer.putInt((int) dataSize);
            buffer.putInt((int) bytesPerSector);
            buffer.putInt((int) sectors);
            buffer.putInt((int) heads);
            buffer.putInt((int) cylinders);
            buffer.flip();
            channel.write(buffer);
            if (comment.length > 0) {
                channel.write(ByteBuffer.wrap(comment));
            }
            // Note: Data segment not handled; append if needed
        }
    }
}

Example usage: HDIFile hdi = new HDIFile(Paths.get("example.hdi")); hdi.printProperties(); hdi.write(Paths.get("modified.hdi"));

  1. JavaScript class for .HDI file handling (node.js, using fs):
const fs = require('fs');

class HDIFile {
    constructor(filepath = null) {
        this.filepath = filepath;
        this.reserved = 0;
        this.fddType = 0;
        this.headerSize = 0;
        this.dataSize = 0;
        this.bytesPerSector = 0;
        this.sectors = 0;
        this.heads = 0;
        this.cylinders = 0;
        this.comment = Buffer.alloc(0);
        if (filepath) {
            this.read(filepath);
        }
    }

    read(filepath) {
        this.filepath = filepath;
        const buffer = fs.readFileSync(filepath);
        const view = new DataView(buffer.buffer);
        this.reserved = view.getUint32(0, true);
        this.fddType = view.getUint32(4, true);
        this.headerSize = view.getUint32(8, true);
        this.dataSize = view.getUint32(12, true);
        this.bytesPerSector = view.getUint32(16, true);
        this.sectors = view.getUint32(20, true);
        this.heads = view.getUint32(24, true);
        this.cylinders = view.getUint32(28, true);
        if (this.reserved !== 0) {
            console.log('Warning: Reserved field is not zero');
        }
        if (this.headerSize > 32) {
            this.comment = buffer.slice(32, this.headerSize);
        }
    }

    printProperties() {
        console.log(`Reserved: ${this.reserved}`);
        console.log(`FDDType: ${this.fddType}`);
        console.log(`HeaderSize: ${this.headerSize}`);
        console.log(`DataSize: ${this.dataSize}`);
        console.log(`BytesPerSector: ${this.bytesPerSector}`);
        console.log(`Sectors: ${this.sectors}`);
        console.log(`Heads: ${this.heads}`);
        console.log(`Cylinders: ${this.cylinders}`);
        const commentStr = this.comment.toString('utf-8').trim() || 'None';
        console.log(`Comment: ${commentStr}`);
    }

    write(outputPath = null) {
        if (!outputPath) {
            outputPath = this.filepath || 'output.hdi';
        }
        const header = Buffer.alloc(32);
        const view = new DataView(header.buffer);
        view.setUint32(0, this.reserved, true);
        view.setUint32(4, this.fddType, true);
        view.setUint32(8, this.headerSize, true);
        view.setUint32(12, this.dataSize, true);
        view.setUint32(16, this.bytesPerSector, true);
        view.setUint32(20, this.sectors, true);
        view.setUint32(24, this.heads, true);
        view.setUint32(28, this.cylinders, true);
        const fullBuffer = Buffer.concat([header, this.comment]);
        fs.writeFileSync(outputPath, fullBuffer);
        // Note: Data segment not appended; handle separately
    }
}

module.exports = HDIFile;

Example usage: const HDIFile = require('./hdi.js'); const hdi = new HDIFile('example.hdi'); hdi.printProperties(); hdi.write('modified.hdi');

  1. C++ class for .HDI file handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>

class HDIFile {
private:
    std::string filepath;
    uint32_t reserved = 0;
    uint32_t fddType = 0;
    uint32_t headerSize = 0;
    uint32_t dataSize = 0;
    uint32_t bytesPerSector = 0;
    uint32_t sectors = 0;
    uint32_t heads = 0;
    uint32_t cylinders = 0;
    std::vector<uint8_t> comment;

public:
    HDIFile(const std::string& fp = "") : filepath(fp) {
        if (!fp.empty()) {
            read(fp);
        }
    }

    void read(const std::string& fp) {
        filepath = fp;
        std::ifstream file(fp, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        uint32_t header[8];
        file.read(reinterpret_cast<char*>(header), 32);
        if (file.gcount() < 32) {
            throw std::runtime_error("Invalid HDI file: Header too short");
        }
        reserved = header[0];
        fddType = header[1];
        headerSize = header[2];
        dataSize = header[3];
        bytesPerSector = header[4];
        sectors = header[5];
        heads = header[6];
        cylinders = header[7];
        if (reserved != 0) {
            std::cout << "Warning: Reserved field is not zero" << std::endl;
        }
        if (headerSize > 32) {
            comment.resize(headerSize - 32);
            file.read(reinterpret_cast<char*>(comment.data()), headerSize - 32);
        }
    }

    void printProperties() const {
        std::cout << "Reserved: " << reserved << std::endl;
        std::cout << "FDDType: " << fddType << std::endl;
        std::cout << "HeaderSize: " << headerSize << std::endl;
        std::cout << "DataSize: " << dataSize << std::endl;
        std::cout << "BytesPerSector: " << bytesPerSector << std::endl;
        std::cout << "Sectors: " << sectors << std::endl;
        std::cout << "Heads: " << heads << std::endl;
        std::cout << "Cylinders: " << cylinders << std::endl;
        std::string commentStr(comment.begin(), comment.end());
        std::cout << "Comment: " << (commentStr.empty() ? "None" : commentStr) << std::endl;
    }

    void write(const std::string& outputPath = "") const {
        std::string out = outputPath.empty() ? (filepath.empty() ? "output.hdi" : filepath) : outputPath;
        std::ofstream file(out, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot write file");
        }
        uint32_t header[8] = {reserved, fddType, headerSize, dataSize, bytesPerSector, sectors, heads, cylinders};
        file.write(reinterpret_cast<const char*>(header), 32);
        if (!comment.empty()) {
            file.write(reinterpret_cast<const char*>(comment.data()), comment.size());
        }
        // Note: Data segment not written; append if needed
    }
};

Example usage: HDIFile hdi("example.hdi"); hdi.printProperties(); hdi.write("modified.hdi");