QEMU VGA BIOS compatibility
Purpose
The full 16-color EGA target uses QEMU as an observation environment. QEMU’s bundled VGA BIOS does not reproduce one BIOS font-vector behavior required by the observed 2.936 interpreter, causing inverse text to appear as repetitions of one glyph. This chapter documents the fault, the evidence distinguishing it from game corruption, and the reproducible firmware workaround used by the test harnesses.
The workaround patches emulator firmware only. It does not alter a game, interpreter executable, resource, FreeDOS disk image, or clean-room behavioral specification.
Observable symptom
Ordinary white-on-black graphics text renders correctly. Black-on-white dialog text instead contains repeated copies of one symbol. Pictures, views, palette, window borders, and text placement remain correct.
The repeated symbol is significant: it is the VGA BIOS’s compiled-in glyph for the character code selected by the interpreter. It is not random memory and is not evidence of damaged game data.
Rejected causes
The investigation tested and rejected these explanations:
| Hypothesis | Evidence |
|---|---|
The font at F000:FA6E is absent or corrupt. | The system BIOS already contains the correct 1024 bytes, matching the font returned by the VGA BIOS. |
| The interpreter cannot read the font because of i440FX shadow settings. | Read-only, write-only, and read/write PAM0 experiments did not change the result; live memory retained the correct bytes. |
| Copying the font after DOS boot repairs the problem. | A corrected and byte-verified copy still produced the repeated glyph. |
| The selected emulated VGA card is responsible. | QEMU standard VGA and Cirrus VGA reproduced the same behavior. |
| SeaVGABIOS alone is responsible. | The older LGPL VGABIOS 0.7a binary reproduced the failure too. |
The first FIXAGI.COM experiment also contained an independent bug: it changed
DS before reading its saved source offset and consequently copied unrelated
ROM bytes. Correcting the order and verifying the copy repaired the helper but
not the dialog, which separated that helper defect from the actual firmware
incompatibility.
Decisive interpreter trace
For an inverse character, the interpreter performs this observable sequence at the BIOS boundary:
- Read the selected eight-byte glyph from the platform font.
- Copy it to a scratch glyph and invert all eight bytes.
- Temporarily redirect interrupt vector
43hso character80haddresses that scratch glyph. - Ask
INT 10h/AH=09hto draw character80hwith the requested attribute. - Restore vector
43h.
On the first dialog S, the source bytes were:
78 cc e0 70 1c cc 78 00
The scratch bytes after inversion were:
87 33 1f 8f e3 33 87 ff
Immediately before the BIOS call, vector 43h, the eight-byte character
height, and character code 80h resolved exactly to the inverted scratch
bytes. A hardware read watchpoint on those bytes did not trigger during the
BIOS call. The game-side data and vector were correct; the VGA BIOS did not
consult the live vector.
Inspection of LGPL VGABIOS 0.7a independently confirmed that its planar, CGA, and linear graphics character renderers use private compiled-in font arrays. The project’s EGA-only correction therefore changes the planar renderer’s glyph-byte source while retaining its existing plane, mask, attribute, destination, and pixel-writing behavior.
Repository artifacts
| Path | Policy |
|---|---|
third_party/vgabios/vgabios-0.7a.bin | Tracked pristine upstream binary; never modify in place. |
third_party/vgabios/COPYING | Tracked upstream LGPL 2.1 license. |
third_party/vgabios/README.md | Tracked provenance, source availability, and checksums. |
tools/vgabios_int43_patch.asm | Tracked 16-bit patch source. |
tools/setup_vgabios.py | Tracked deterministic validator and patch builder. |
build/vgabios/vgabios-0.7a-int43.bin | Generated patched ROM; ignored and never committed. |
The pristine binary is 41,472 bytes with SHA-256:
cd9fdd6a789dcd22b8a6b3b152788d43238de49cce674cff57bdeb94580246c6
The deterministic patched binary has SHA-256:
cfbbc5e3f97cb40cbc315b68e1e52d4488e6e27a47b339452a6a4ebf00f01247
Patch construction
The builder performs all of these checks and transformations:
- Verify the complete pristine-ROM SHA-256.
- Assemble
tools/vgabios_int43_patch.asmwith NASM and require exactly 44 bytes. - Verify the original planar glyph-fetch instruction bytes at ROM offset
0x5251. - Verify that the destination range beginning at
0xa1a4is unused padding. - Replace the original glyph calculation with a near call and padding.
- Install the patch routine at
0xa1a4. - Recalculate the final option-ROM checksum byte so the complete byte sum is zero modulo 256.
- Verify the complete generated-ROM SHA-256 shown above.
The inserted routine reads the current offset and segment from vector 43h,
adds the selected character, character-height, and glyph-row offsets, fetches
the corresponding byte, applies the existing pixel mask, and returns to the
unchanged renderer.
Reproduction
Build or verify the generated option ROM directly:
python3 -B tools/setup_vgabios.py --force
Normal FreeDOS environment setup performs the same step automatically:
python3 -B tools/setup_freedos_image.py --force
Use --skip-vgabios only for an intentional control environment using QEMU’s
bundled VGA BIOS.
Launch QEMU manually with the generated ROM:
qemu-system-i386 -m 16 -boot c \
-drive file=build/freedos/freedos.img,format=raw,if=ide,index=0,media=disk \
-vga none \
-device VGA,romfile="$(pwd)/build/vgabios/vgabios-0.7a-int43.bin" \
-display cocoa -monitor stdio
The monitor/VNC variant replaces -display cocoa with:
-display vnc=127.0.0.1:5
Shared QEMU harnesses automatically select the generated ROM when it exists.
Set AGI_VGABIOS=/absolute/path/to/another.bin to test another option ROM, or
set AGI_VGABIOS=default to force QEMU’s bundled firmware for a control run.
Validation
The corrected ROM was validated by booting FreeDOS, running the immutable SQ2
input without FIXAGI.COM, and advancing through the introduction, station
scene, and default-name prompt. The first playable-room dialog rendered its
complete black-on-white text with distinct glyphs.
Automated validation includes:
- complete pristine binary size and digest checks;
- exact call-site and unused-area guards;
- oversized-patch rejection;
- generated option-ROM checksum verification;
- deterministic final digest verification;
- QEMU argument selection, override, and explicit-default tests; and
- the complete local compatibility suite and both mdBooks.
If inverse text regresses, first run python3 -B tools/setup_vgabios.py --force and compare the printed output digest with the value above. Then check
the QEMU command for both -vga none and the explicit VGA,romfile=...
device. Merely placing a ROM in build/ does not make a manually constructed
QEMU command use it.
License and source availability
VGABIOS 0.7a is redistributed under the GNU Lesser General Public License,
version 2.1. The repository stores the unmodified upstream binary and license.
third_party/vgabios/README.md identifies the official Savannah binary and
complete source archive and records SHA-256 values for both. The modified ROM
is generated locally from the tracked patch source and is intentionally not
stored in version control.