Skip to content

dosforge 0.9.52

Choose a tag to compare

@github-actions github-actions released this 14 Jun 00:43
· 11 commits to main since this release

dosforge v0.9.52

Fix: Windows path FreeDOS FAT32 / MS-DOS 7.10 boot for VHDs > 504 MiB

User-reported 86Box symptom: freshly built Windows-side VHDs hung at a
blinking cursor right after POST. Affected:

  • FreeDOS FAT16 and FAT32 (any size that pushed footer cylinder count

    1024 — typically anything ≥ 512 MiB at 16h/63s).

  • MS-DOS 7.10 (OSR2) FAT16 (showed "Missing operating system" — same
    underlying bug, different MBR-boot-code failure path).

Root cause: ECHS-doubled start_chs invalid for the BIOS that

actually runs

src/dosforge/disk.py (Windows VHD path, _create_and_prepare_vhd_no_kernel)
contained an ECHS bit-shift loop:

partition_chs_heads = footer.heads
if footer.heads > 0 and footer.cylinders > 1024:
    heads = footer.heads
    cyls = footer.cylinders
    while cyls > 1024 and heads < 256:
        heads *= 2
        cyls = (cyls + 1) // 2
    partition_chs_heads = min(heads, 255)

For a 1 GiB VHD with footer (2081, 16, 63), this loop runs twice and
produces partition_chs_heads = 64. _lba_to_chs(2048, heads=64, spt=63) then encodes start CHS as bytes 20 21 00 → (cyl=0,
head=32, sec=33).

The MBR boot loader (440-byte _BUILTIN_MSDOS_MBR_BOOT_CODE) reads
the partition's first sector via INT 13h AH=02 DH=32 (head 32).
86Box's AUTO IDE — and every modern emulator/BIOS preset that
reports the disk's raw geometry rather than auto-translating —
rejects head 32 as out of range for a 16-head disk. The carry flag
gets set, the MBR's JC short-circuit fires, MBR halts at its
blinking-cursor loop.

For MS-DOS 7.10 the same bad CHS triggers the secondary "no
bootable VBR found" path in OSR2's MBR loader → "Missing operating
system".

Why this is a Windows-only regression

The Linux path was given exactly this fix two weeks ago in commit
3951021 (v0.6.3)
. Quoting that commit's rationale:

parted writes the partition entry with its own BIOS-canonical CHS
values that don't correspond to the VHD footer geometry. On 86Box
AUTO IDE, the mismatch causes the BIOS to pick LRG translation as
a fallback; INT 13h reads then land on the wrong sectors and the
disk fails to boot with 'Missing operating system'.

Validated empirically: 4 of 8 boot-success VHDs from the v0.5.2
smoke matrix went from 'Missing operating system' to bootable
after the manual equivalent of this patch.

The Linux fix introduced _rewrite_mbr_partition_entry_for_footer
which uses footer.heads / footer.sectors_per_track directly (no
ECHS doubling) and runs after parted clobbers the partition table.
That commit explicitly noted "The defensive ECHS-translation logic
at the existing core_mbr.write_single_partition_mbr call site was
unreachable on Linux because parted clobbers the partition table
after that call runs"
— meaning the Linux author knew the Windows
code path still had ECHS doubling, but didn't fix it because Linux
tests had already moved past the issue.

The Windows path slipped through v0.6.3 → v0.9.51 because:

  1. Most Windows smoke tests in v0.6 / v0.7 / v0.8 era used VHDs
    ≤ 504 MiB, where the ECHS loop is a no-op.
  2. FreeDOS FAT32 testing on Windows didn't exercise sizes ≥ 512 MiB
    until the v0.9.30+ Grow VHD UI made it easy to grow past that
    threshold.
  3. v0.9.37 fixed the related end_chs LBA-marker bug (FE FF FF
    clamped to footer geometry) but only on the end_chs side, not
    start_chs.

Why the original ECHS rationale (v0.6.2) was wrong

The June 4, 2026 commit f429eb8 introduced ECHS doubling with the
rationale *"AT BIOSes apply ECHS bit-shift translation for any drive

504 MB"*. That model was correct for pre-1996 AT BIOSes which
auto-translated in hardware. But:

  • Post-1996 BIOSes + modern emulators (86Box AUTO IDE, QEMU IDE)
    present raw disk geometry to DOS and expect the OS to handle
    LBA explicitly via INT 13h AH=42 for transfers.
  • The MBR boot code we install does INT 13h AH=02 (CHS) for the
    one-shot VBR chain read, trusting the BIOS to honor the disk's
    raw geometry.
  • ECHS-translated CHS is therefore invalid on every modern target.

The Linux v0.6.3 fix matched empirical behavior (booted 4/8
previously broken VHDs); the Windows path just never caught up.

Fix

src/dosforge/disk.py — removed the ECHS-doubling loop.
partition_chs_heads now uses footer.heads directly, matching the
Linux _rewrite_mbr_partition_entry_for_footer behavior.

partition_chs_heads = footer.heads
partition_chs_spt = footer.sectors_per_track

XT-class layouts (compaq2, msdos33+MFM, ibm8088+dos33+MFM)
take a separate code path (_rewrite_mbr_for_xt_class) and are
unaffected.

Verified end-to-end on Windows

Fresh FreeDOS FAT32 1 GiB VHD before vs. after:

Field v0.9.51 (broken) v0.9.52 (fixed)
Start CHS bytes 20 21 00 (head=32) 00 21 02 (head=0)
INT 13h AH=02 result "invalid head" error LBA 2048 read OK
86Box AUTO IDE Blinking cursor Boots into FreeDOS
MS-DOS 7.10 FAT16 "Missing operating system" Boots into DOS

Files changed

  • src/dosforge/disk.py — removed partition_chs_heads
    ECHS-doubling loop in _create_and_prepare_vhd_no_kernel;
    expanded the comment to document the cross-platform consistency
    requirement.
  • pyproject.toml + src/dosforge/__init__.py — 0.9.51 → 0.9.52.

Cross-platform parity

After this release, both Linux and Windows paths encode the MBR
partition CHS using the VHD footer's actual geometry. No ECHS
bit-shift translation on either side.

Carried forward

  • v0.9.51 Windows GUI Combobox wheel + focus polish
  • v0.9.50 Stamp release artifacts with correct version
  • v0.9.49 MartyPC-Xebec preset auto-defaults to FAT12
  • v0.9.48 MartyPC compatibility expansion
  • v0.9.47 IBM 8088 'DOS Version' picker + PC-DOS 5.x boot mode
  • v0.9.37 end_chs LBA-marker fix (companion to this start_chs fix)