DOS MZ executable header encoding and decoding.

Every DOS, Windows PE, and OS/2 LE/LX executable opens with a 64-byte little-endian MZ header (named after Mark Zbikowski, its author at Microsoft):

Offset  Size  Field
0x00    2     signature              ("MZ", 0x5a4d)
0x02    2     lastPageBytes
0x04    2     pageCount
0x06    2     relocationCount
0x08    2     headerParagraphs
0x0a    2     minExtraParagraphs
0x0c    2     maxExtraParagraphs
0x0e    2     initialSS
0x10    2     initialSP
0x12    2     checksum
0x14    2     initialIP
0x16    2     initialCS
0x18    2     relocationTableOffset
0x1a    2     overlayNumber
0x1c    8     reserved1
0x24    2     oemIdentifier
0x26    2     oemInfo
0x28    20    reserved2
0x3c    4     newHeaderOffset        (e_lfanew)

Every field is little-endian — DOS executables originate on x86, which is natively little-endian.

newHeaderOffset (commonly called e_lfanew) points past the DOS stub — the tiny 16-bit program that prints "This program cannot be run in DOS mode." — to a richer header such as PE's PE\0\0 signature. This coder only covers the 64-byte MZ header itself: it does not read the DOS stub or follow newHeaderOffset into a PE/LE/LX header. Parsing what lives at that offset is out of scope for v0.0.1.

Examples

Round-trip a minimal header

import { assertEquals } from "@std/assert";
import { dosMzHeader, MZ_HEADER_SIZE, MZ_SIGNATURE } from "@binstruct/dos-mz";

const coder = dosMzHeader();
const header = {
  signature: MZ_SIGNATURE,
  lastPageBytes: 0x90,
  pageCount: 3,
  relocationCount: 0,
  headerParagraphs: 4,
  minExtraParagraphs: 0,
  maxExtraParagraphs: 0xffff,
  initialSS: 0,
  initialSP: 0xb8,
  checksum: 0,
  initialIP: 0,
  initialCS: 0,
  relocationTableOffset: 0x40,
  overlayNumber: 0,
  reserved1: new Uint8Array(8),
  oemIdentifier: 0,
  oemInfo: 0,
  reserved2: new Uint8Array(20),
  newHeaderOffset: 0x80,
};

const buffer = new Uint8Array(MZ_HEADER_SIZE);
const written = coder.encode(header, buffer);
const [decoded, read] = coder.decode(buffer);

assertEquals(written, MZ_HEADER_SIZE);
assertEquals(read, MZ_HEADER_SIZE);
assertEquals(decoded, header);