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.
- 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.
- Two direct download links for files of format .HDI:
- https://archive.org/download/touhou.hdi/touhou.hdi
- https://archive.org/download/pc-98-windows-3.1-hdi-dos-6.2/win31_pc98.hdi
- Ghost blog embedded HTML JavaScript for drag and drop .HDI file dump:
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.
- 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')
- 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"));
- 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');
- 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");