Skip to content

Releases

BM-DOS 2026-11-11.82

Choose a tag to compare

@mmoroca mmoroca released this 13 Nov 13:30
53d3215

BM-DOS 2026-11-11.82: the card's real free space, and copies that actually use the patches

DIR used to report a figure that was written into dosbox-core, not read from the
card: MOUNT's default geometry ("512,32,32765,16000") is 262 144 000 bytes free,
the same number on an empty card and on a full one. That is fixed here, and so is
a design mistake from the previous round: the patches that speed up copying only
took effect if "make fetch" had been run in that tree.

Free space

  • src/platform/bare_freespace.cpp reads the card with f_getfree("SD:") and
    bare_storage_dos_space() hands the four numbers INT 21h AH=36h wants to
    patches/dosbox-core/0004, which is what localDrive::AllocationInfo() returns.
    src/platform/bare_space.h does the arithmetic: it is integers only and
    includes nothing, so the same code runs on the host (make check-storage drives
    it) and on the board.
  • DOS takes those numbers in 16-bit registers, so a card with more than 65535
    clusters is described in coarser ones - 2 GiB with 4 KB clusters shows as
    58254 clusters of 36 KB - and the bytes free are right to within one of those
    clusters. Above about 8.5 GB (65535 clusters of 255 sectors) DOS's own
    ceiling is reached and the figure stops there.
  • -freesize and -size are untouched and they win. MOUNT reads them exactly as
    before; the only thing added is that it remembers whether either was given
    (localDrive::SetSpaceFromPort), so the card is asked only when the mount line
    did not pin the number itself. Measured on the board: -freesize 100 still
    reports 104 857 600. Floppy, overlay and CD/ISO drives are never asked, and
    with no card to ask the old fixed figures stand.
  • Two separate files, not one, and it is not tidiness: addon/fatfs/ff.h has
    "typedef struct DIR DIR" and newlib has its own DIR, so the file that calls
    f_getfree() cannot include anything else.

The patches, applied by every build

  • patches/dosbox-core/ holds the three patches that made copying from a mounted
    ISO fast: 0001 asks for contiguous sectors on IMGMOUNT, 0002 raises COPY's
    buffer, 0003 makes isoFile::Read() ask for the whole run of sectors at once
    instead of one 2048-byte ReadSector() per sector (30 reads of 2048 bytes become
    1 read of 61440 per buffer).
  • The mistake: they were applied by "make fetch" only. A tree that already had
    third_party/dosbox-core compiled the core without them and nothing said so -
    a quiet difference is worse than a loud error. Now core-patch is a prerequisite
    of every build, it is idempotent, silent when there is nothing to do, and a core
    that is not in the state these patches make is set up again from the pinned
    revision. make deps says "core patches: 4 of 4 applied".
  • Unrelated to the card size: 0003 is measured, not assumed. The symptom that
    started it was "the copies are still slow, especially with many small files",
    and the reason is that a .iso mounted with -t iso is an isoDrive, whose reads go
    through ReadSector() (singular); 0001 had only touched ReadSectors() (plural),
    the path a MSCDEX CD device takes. The patch was in the wrong path, not too
    small.

The watchdog, and a clock for copies

  • The shell's own commands run inside the emulated INT 2Eh, so a four-second COPY
    executes no emulated instruction and no frame happens while it runs. The
    watchdog's two seconds therefore took the picture away in the middle of ordinary
    copies and printed four lines about a stall that was not one. It is ten
    seconds now, still short enough to be impossible to miss when the emulator
    really has died, because that one never resumes.
  • Every core call over 250 ms now leaves "HOST: one core call took N ms" in the
    log (PetSis), at most one a second. A shell command is one core call, so that
    number is how long the whole command took - and it is the only clock a copy
    has, because DOS's clock does not move while the emulator is stopped.

Docs and tests

  • README (both languages) and sd/dosbox.conf: how the free figure is worked out
    and why it can be coarser than the card, that -freesize and -size still win,
    and what the watchdog and the HOST: line mean during a copy.
  • make check-storage: six geometries through the conversion - 250 MB unscaled,
    2 GiB becoming 58254 clusters of 72 sectors (half free -> 1 073 737 728 B),
    32 GB hitting the ceiling, a FSInfo that claims more free clusters than exist,
    and four cases that must refuse to answer.
  • make check-host: 13 targets, 452 ok lines, 0 failures.

What is not verified here

  • The AArch64 build and the board: this workspace has no toolchain and no card.
    The patched core files are compiled with g++ -fsyntax-only on the host, and
    bare_freespace.cpp is compiled against the shape of ff.h (csize, n_fatent,
    f_getfree), not against the real header.
  • The speed of a copy after 0003: the patch removes the per-sector reads, but how
    much that is worth on this card is a measurement, not a claim.

Co-authored-by: arena.ai

BM-DOS 2026-11-11.80

Choose a tag to compare

@mmoroca mmoroca released this 12 Nov 00:43

BM-DOS 2026-11-11.80: General MIDI music with a SoundFont

Until now the [midi] section was a dead end: no host MIDI, no MT-32, and a game
that asked for General MIDI got nothing. This release answers that with a
synthesiser built into the kernel that reads a .sf2 file from the card. Tested with GeneralUser-GS.sf2 (https://github.com/mrbumpy409/GeneralUser-GS).

MIDI

  • src/dosbox-stub/midi_stub.cpp replaces src/gui/midi.cpp: it defines
    MIDI_Init, MIDI_RawOutByte, MIDI_Available and MidiHandler::MidiHandler(),
    which is all the core asks for. The emulated MPU-401 hands bytes to it and
    TinySoundFont (src/dosbox-stub/tsf.h, header only) turns them into sound on a
    mixer channel called TSF at 44100 Hz.
  • Configuration is two keys: mpu401=intelligent and midiconfig=<name or path>.
    A bare name is looked up in /dosbox/system/soundfonts/ on the card, then
    without the SD: prefix, then at the root; SD:/... and /... are taken as
    written. No .sf2 ships in the image (size and licence are the author's).
  • All 16 channels are pre-initialised, so a game whose first message is a Note
    On (no Program Change) is not silent; channel 10 starts on the drum bank.
  • CC is forwarded verbatim to TinySoundFont, which resolves sustain, volume,
    pan, expression, bank select and RPN 0/1/2 (pitch range, fine and coarse
    tune). Note On/Off, Program Change, 14-bit Pitch Wheel, System Reset, GM
    System On and GS Reset are handled; other SysEx is dropped.
  • midi.handler is now assigned, so MIXER /LISTMIDI lists "TSF <path> (<n> presets)". Subclassing MidiHandler emits the base vtable and that pulled in
    GFX_ShowMsg (src/gui/sdlmain.cpp, not compiled here) through an inline
    LOG_MSG; the macro is neutralised around the include and nm confirms zero
    references.

Reading the SoundFont

  • Measured on the board first, with a 31559 KB SoundFont: 9863 ms of fread
    against 501 ms inside tsf. The parse was never the problem - one FatFs call
    per kilobyte was, which is what newlib's stdio does with a large fread.
  • A bigger stdio buffer was tried and broke the load outright (tsf_load
    returned NULL on the file that had just loaded), which says the trouble is
    below stdio. So the bulk of the file no longer goes through stdio's buffer:
    a 64 KB buffer of our own, ALIGNED to 64 bytes (large transfers to the SD
    driver end in DMA, and malloc only guarantees 16), the stream in _IONBF so a
    block is one call down to FatFs, and tsf reads through its own read/skip
    pair, served from that block. Measured after: 3836 ms, 8.4 MB/s.
  • The file is never held in RAM: it is parsed with tsf_load() over that stream,
    not tsf_load_memory.
  • A block that comes back short is retried in place - up to twice, in 4 KB
    pieces - because on this card a 64 KB multi-sector read can fail with EIO
    while smaller reads go through. Only if that fails does it fall back to the
    plain streaming path, which says so and is 2.6 times slower. The log says
    it once, not once per attempt: a failed fread leaves stdio's error flag set
    and the next one fails at the same offset, which is where the two identical
    lines came from.
  • Messages name the reason instead of saying "failed": the offset, and either
    (errno N) or (file ends there) - decided with ferror()/feof(), because a
    short read at end of file leaves errno holding an older call's number.
  • "... , N block retries" is appended to the timing line when a boot needed
    any. Same file and same code: a count that grows boot after boot, or the
    same offset every time, is the card; a count that disappears was a bad
    contact. The port only calls fread(), FatFs already clips each disk_read at
    the cluster boundary, and Circle retries the data command three times before
    reporting anything - an error that reaches fread has already failed three
    times below this code.
  • The SoundFont is loaded once per boot. MIDI_Init runs twice (libretro.cpp's
    second check_variables() pass updates mt32.romdir), so the parsed
    synthesiser is kept in RAM when the resolved path has not changed, and a
    path that failed is not read again in the same boot.

Memory

  • The size of the SoundFont is the user's choice, and TinySoundFont turns every
    16-bit sample into a float, so the instrument costs about twice the file.
    On this board an exhausted heap does not return NULL: Circle sets the low
    heap up with a 256 KB reserve (lib/memory.cpp:100, heapallocator.h) and
    panics. BareSysInfoHeapFree() returns GetHeapFreeSpace(HEAP_ANY) and
    OpenStage() refuses a file that needs more than two and a half times its own
    size, in one short line, before tsf allocates anything. If Circle answers 0
    the check is skipped, and the game keeps its OPL music either way.

Docs and tests

  • READMEs: a "MIDI with a SoundFont" section (how to enable it, path rules,
    the four lines it prints, the memory limit, what an errno 5 means and what
    is not implemented), and the feature table no longer says MIDI is missing.
  • sd/dosbox.conf documents the [midi] section instead of declaring it absent;
    it stays off by default because no .sf2 ships with the image.
  • make check-sf2reader: the reader that ships, extracted verbatim from
    midi_stub.cpp by scripts/hostcheck/gen-sf2reader.py, driven over files the
    test writes itself - block boundaries, 1-byte reads, skips that cross the
    window, one 500 KB read, a file shorter than it claims, the RAM guard, and a
    synthetic EIO (fopencookie) that recovers once and one that never does.
    61 checks, and it fails loudly if the markers it extracts disappear.
  • scripts/hostcheck/setup.sh now generates scripts/hostcheck/circle/memory.h.
    It was written by hand in .79c, and that directory is generated and
    git-ignored, so a clean clone of the published branch had no such header and
    check-platform/check-kernel could not compile bare_sysinfo.cpp. Found by
    diffing this tree against a clean clone of the branch, and verified by
    deleting circle/ entirely and running check-host from scratch.
  • .gitignore lists the working scripts that live outside the tree, so
    crear-mensaje-commit.sh cannot be committed again.

Co-authored-by: arena.ai

BM-DOS 2026-11-10.78

Choose a tag to compare

@mmoroca mmoroca released this 10 Nov 12:10

BM-DOS 2026-11-10.78: cmdline.txt owns the display mode, governor and boot fixes

Display mode

Circle creates its own console framebuffer before this port creates its own, and
its size comes from width= / height= in cmdline.txt, not from boot.conf. Two
different geometries meant a mode change at run time, and that is what left the
display scanning memory that still held the console's text.

  • sd/cmdline.txt reduced to ONE line, under 2048 bytes. Circle's parser splits
    on spaces only, and drops the whole file above 2048 bytes
    (TPropertyTagCommandLine::String[2048]), silently: a commented cmdline.txt
    was a cmdline.txt whose options were read as part of the last comment.
  • make-sd.sh and update-sd.sh write width= and height= from boot.conf,
    collapse the file to one line, and refuse to write anything over the limit.
  • make-sd.sh now copies boot.conf (it never did, so a fresh card fell back to
    the built-in 1280x720).
  • cmdline.txt has the LAST WORD on the display mode, and that is now what the
    kernel does. It used to compare the console's physical mode against
    boot.conf and refuse to boot when they disagreed, which turned a cosmetic
    disagreement between two text files into a dead board. It now prints both
    numbers, says which file it is using, adopts the console's geometry and
    carries on. boot.conf keeps its display= because the scripts need a source
    for width=/height= at card-build time; it is a claim, not an order.
  • A comment that had been lying for three rounds is fixed: CBcmFrameBuffer's
    GetWidth() returns the number that went INTO the request, not what the
    firmware granted. With width=/height= present the two coincide; without
    them the constructor fills the width from the display dimensions, which
    arrive with the overscan border already subtracted - that is where the
    1824x984 came from.
  • READMEs: a new "Configuration: cmdline.txt" section - one line, spaces only,
    2048 bytes, the exact line this project writes, and a table of the six
    options that matter here with Circle's real ranges (loglevel 0..5 is a
    severity ceiling and NOT a destination; keymap is the physical layout, not
    the DOS one; socmaxtemp above 78 is ignored by Circle, so the 80 on the card
    changes nothing). Everything else links to Circle's doc/cmdline.txt, with a
    note on why fast= and sounddev= do nothing here.
  • sd/config.txt: disable_overscan=1. The firmware subtracts a 48 px border per
    edge in CEA modes, which is the 1824x984 that appeared on the board.

Clock governor

  • Idleness is decided by DURATION, not by counters. Three rules were tried on
    the board and two of them were mine and wrong:
    • "identical frame is idle" walked a Pi 3 down to 600 MHz in the middle of
      DOOM, because a game repeats its picture too; the audio broke up.
    • "identical and cheap is idle" fixed DOOM and pinned a DOS prompt at the
      top level at 63 C, because most of a 42 ms text frame is the emulated
      machine sleeping on its own timer, not working.
    • "advanced fewer times than the mode asks" was withdrawn before it shipped:
      a healthy machine reads 30/60 or 35/70, so the test is meaningless.
      What works is waiting ten seconds of a literally identical picture
      (governor::IdleFramesForPrompt). A prompt does that; no game does. Being
      wrong costs one level for three seconds.
  • IsStalledFrame() and IsWaitingFrame() removed, and the governor tick is back
    to three arguments: the counters cannot separate idle from working.
  • BuildLevels() now guarantees a strictly increasing table. A ceiling below
    1500 used to produce {600, ceiling, ceiling} with two levels, so the governor
    believed it could step down while SetLevel() moved nothing.

Boot screen

  • Banner() removed: it painted the version block on Circle's console and
    Report() painted it again on ours. The nine key-help lines went the same way.
  • BareVideoStatus() erases the line before drawing it, so a replaced message
    no longer overlaps the one before it.
  • boot_log = auto | quiet | off in boot.conf: how much of the boot is painted,
    never what is logged.

Build and checks

  • check-kernel.sh compiles to an object instead of -fsyntax-only: gcc only
    reports -Wunused-function when it generates code.
  • setup.sh applies patches/*.patch automatically, so a clone works on a Pi 3
    without a manual step.
  • Sources and script output in English.
  • READMEs: display mode by board, the cycles table by emulated CPU, and a
    "Lessons learned" section with the platform traps (cmdline.txt, the two
    framebuffers, overscan, -fsyntax-only, idle detection, single core).

Co-authored-by: arena.ai