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.
Round-trip a minimal header
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);