#!/usr/bin/env python3 """Assemble the .COM demos with NASM and run them inside DOSBox (headless). DOSBox provides a real MS-DOS-compatible INT 21h implementation (and its own INT 3 default vector), so this is where the INT 21h and INT 3 behaviour is actually executed. """ import os, pathlib, re, shutil, subprocess, sys, tempfile ROOT = pathlib.Path(__file__).resolve().parent.parent ASM, BUILD, OUT = ROOT / "asm", ROOT / "build", ROOT / "output" BUILD.mkdir(exist_ok=True); OUT.mkdir(exist_ok=True) PROGRAMS = { # source -> DOS 8.3 name "hello21": "HELLO21.COM", "fn_tour": "TOUR.COM", "int3_handler": "I3H.COM", "int3_default": "I3D.COM", "dbg_cc": "DBGCC.COM", "dbg_cd03": "DBGCD.COM", } def nasm(src, out, listing=None): cmd = ["nasm", "-f", "bin", str(ASM / (src + ".asm")), "-o", str(out)] if listing: cmd += ["-l", str(listing)] subprocess.run(cmd, check=True, cwd=ASM) def clean(text): text = text.replace("\r\n", "\n").replace("\r", "\n") return "\n".join(l.rstrip() for l in text.split("\n")).rstrip("\n") + "\n" def main(): # 1. opcode listing (assembled, never executed) nasm("opcodes", BUILD / "opcodes.bin", OUT / "01-opcodes.lst") lst = (OUT / "01-opcodes.lst").read_text() lst = "\n".join(l for l in lst.split("\n") if l.strip()) (OUT / "01-opcodes.lst").write_text(lst + "\n") work = pathlib.Path(tempfile.mkdtemp(prefix="dosbox_")) for src, dos in PROGRAMS.items(): nasm(src, work / dos) have_debug = (BUILD / "DEBUG.COM").exists() if have_debug: shutil.copy(BUILD / "DEBUG.COM", work / "DEBUG.COM") (work / "IN.TXT").write_bytes(b"ZAnkur\r\n") # stdin for fn_tour: AH=01h reads Z, AH=0Ah reads Ankur # DEBUG script: list code, run to the breakpoint, show registers, single-step once, show registers, run on, quit (work / "DC.TXT").write_bytes(b"u 100 L9\r\ng\r\nr\r\nt\r\nr\r\ng\r\nq\r\n") steps = [ ("HELLO21.COM > O1.TXT", None), ("if errorlevel 7 echo ERRORLEVEL is at least 7 >> O1.TXT", None), ("TOUR.COM < IN.TXT > O2.TXT", None), ("I3H.COM > O3.TXT", None), ("I3D.COM > O4.TXT", None), ] if have_debug: steps += [("DEBUG DBGCC.COM < DC.TXT > O5.TXT", None), ("DEBUG DBGCD.COM < DC.TXT > O6.TXT", None)] conf = "[sdl]\nfullscreen=false\n[autoexec]\nmount c %s\nc:\n%s\nexit\n" % (work, "\n".join(s for s, _ in steps)) (work / "run.conf").write_text(conf) env = dict(os.environ, SDL_VIDEODRIVER="dummy", SDL_AUDIODRIVER="dummy") subprocess.run(["dosbox", "-conf", str(work / "run.conf"), "-noconsole"], env=env, timeout=180, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) names = {"O1.TXT": "02-hello21.txt", "O2.TXT": "03-fn-tour.txt", "O3.TXT": "04-int3-handler.txt", "O4.TXT": "05-int3-default.txt", "O5.TXT": "06-debug-cc.txt", "O6.TXT": "07-debug-cd03.txt"} for src, dst in names.items(): p = work / src if p.exists(): (OUT / dst).write_text(clean(p.read_text(errors="replace"))) else: print("missing", src, "(DEBUG.COM not fetched? run scripts/fetch-debug.sh)", file=sys.stderr) shutil.rmtree(work, ignore_errors=True) if __name__ == "__main__": main()