ethernetgateway v0.8.0
Added
-
CP/M emulator — Z80 CPU + interactive console (in progress). A
new default-off config keycpm_emu_enabled(wired into the telnet, web, and
GUI config UIs) gates aK CP/M Systemmain-menu item. Selecting it boots a
real Z80 CPU (the BSD-licensediz80crate)
in a 64 KB machine driven by our own CP/M 2.2 BDOS, and drops into a Rust
CCP-liteA>prompt. The full console BDOS group (character/string output,
console input with echo, direct console I/O, read-console-buffer, console
status, version) is wired to the telnet/SSH session, so interactive Z80
programs can read and write the console; built-inHELLOandECHOdemos
exercise it. Works correctly on PETSCII (C64) terminals as well as ANSI/ASCII.
On launch it creates the drive foldersCPM/A..CPM/Hundertransfer_dir.
An interactive program can be aborted with a double-ESC, and a runaway is
bounded by an instruction budget. Completely separate from the Gateway Shell,
which emulates no CPU; the item is hidden and the key rejected while the
toggle is off.- Filesystem, part 1 — FCB + drives + sequential file I/O. The emulator
now has a directory-backed CP/M filesystem: each drive A:–H: is a folder in
theCPM/container, and the BDOS file calls for opening, creating,
closing, and sequentially reading/writing files (via the DMA buffer and a
parsed 36-byte FCB) are implemented and jailed undertransfer_dir. Drive
select / current-disk / set-DMA are wired, 8.3 filenames are enforced
(case-insensitive; host files that aren't valid 8.3 are invisible to CP/M),
and theA>prompt gainedA:..H:drive-change commands with a
drive-aware prompt. - Filesystem, part 2 — directory search + erase. The BDOS
search-first/search-next calls enumerate a drive's files (with?
wildcards, synthesizing CP/M directory entries per 16 KB extent) and the
delete call removes matching files. The CCP-lite gained the authentic
built-insDIR(list the current drive) andERA name(erase, with a
*.*confirmation), so files uploaded into aCPM/drive can be listed
and removed interactively. - Filesystem, part 3 — random-record I/O, file size, rename. The BDOS
random read/write calls seek to a record number (with the sequential
position kept in sync), compute-file-size reports a file's length in
records, and rename moves a file to a new 8.3 name (no clobber). This
completes the CP/M 2.2 file BDOS surface, so real utilities likePIP
andSTATbecome runnable once.COMloading lands. - Run a real
.COMfrom a drive. A command at theA>prompt that
isn't a built-in is now resolved as<verb>.COMon the drive (honoring a
B:drive prefix), loaded into the TPA, and executed — so actual CP/M
software (PIP, STAT, ASM, …) uploaded into aCPM/drive runs over
telnet/SSH. Page zero is set up exactly as the CCP does before launch: the
command tail is placed at 0x0080 and the first two arguments are parsed
into the default FCBs at 0x005C / 0x006C. The program image is loaded
jailed undertransfer_dir(canonical-prefix + symlink checks) and bounded
by the per-file size cap. - Full CP/M resident command set. The
A>prompt now implements all
six authentic CP/M 2.2 resident commands —DIR,ERA,REN,TYPE,
SAVE,USER(plus thed:drive change) — so no upload is needed for
everyday file work.RENrenames (no clobber);TYPEstreams a text file
and stops at the^Zend-of-file marker (binary files refused);USER
selects an area (only area 0 exists — one flat area per drive). To make
SAVEauthentic, the emulated machine now stays resident across commands:
the transient program area survives a warm boot back toA>(as on real
CP/M), soSAVE n filedumps the image a previous program (e.g.DDT)
left in memory. Low-memory vectors are reinstalled on each program load so
a program that trashes page zero can't corrupt the next one. - Virtual-modem UART port selection. A new config key
cpm_emu_uart
(wired into the telnet, web, and GUI config UIs, each showing a description
beside every choice) selects which machine/port address the emulated CP/M's
modem answers at —off(default), the RC2014/RomWBW Z80 SIO/2 channels
(rc2014_1a…rc2014_2b, 0x80–0x87), or the Altair 88‑2SIO / 88‑SIO
(altair_2sio1/altair_2sio2/altair_sio). Addresses and status-bit
conventions are taken from the RomWBW SIO driver and David Hansel's Altair
simulator. With a profile selected,CpmMachine's port I/O answers at those
addresses with a valid idle UART (transmit ready, nothing received) so
comms software can probe and initialise the port. - Virtual modem — outbound dialling. The CP/M modem now speaks Hayes
ATand can place calls:ATD A/ATD Bdial the gateway's own serial
Port A / Port B (via the existing peer-dial plumbing, like one machine
calling another), andATDT host:portopens a TCP connection. On answer it
reportsCONNECTand becomes a transparent data pipe;+++returns to
command mode andATHhangs up. A newauxprofile choice puts the modem
on the CP/M BDOSAUX:device (functions 3/4) — the hardware-independent
path for SC126/RomWBW software (a Z180 ASCI port profile can't work: the
Z80 core doesn't implement the Z180IN0/OUT0instructions the ASCI
uses). The modem is a self-contained async layer bridged to the guest's
synchronous UART/AUX byte rings at the CPU batch seam.
- Filesystem, part 1 — FCB + drives + sequential file I/O. The emulator
-
EGT80 — "Ethernet Gateway Terminal", a CP/M terminal of our own. Every
period CP/M terminal is built for one machine's serial port — commonly a
separate binary per port, or a build for one specific card, and some generic
builds carry no serial driver at all — and pairing one with the wrong port
produces silence rather than an error.EGT80.COM(newEGT80/directory,
Z80 assembly, CP/M 2.2 and CP/M 3) asks instead: a menu picks the port at run
time, from five families — Z80 SIO/2, 6850 ACIA, RomWBW HBIOS (RST 8),
Z180 ASCI and the CP/MAUX:device — with a free-form address entry for
boards at unusual ports. It carries a console layer chosen once at startup
(BIOS vectors on 2.2, BDOS 6 on 3), menus and help written for someone who
does not yet know which port their machine uses, terminal mode with an
escape-key menu, an ANSI/ASCII inbound filter (pass escape sequences through,
or strip them and the high bit for a printing terminal), settings saved into
its own.COM(a 128-byte-aligned patch area rewritten with a
random-record write, signature- and range-checked at startup and falling back
to defaults if damaged), and XMODEM transfer both ways — 128-byte blocks,
CRC-16 with the checksum fallback an older peer may insist on, one buffer
serving as both protocol block and CP/M record, abortable with a keypress, and
refusing to leave a partial file behind when a download fails.Shipped ready to use: the
.COMis compiled into the gateway binary and
placed on CP/M drive A: when the emulator first creates its drive folders, so
it is simply there. That copy is never overwritten afterwards, because the
settings live inside it; deleting it restores the shipped copy. Release
archives also carry the looseEGT80.COMfor sending to real hardware.The port screen names the port in force and offers
D— the default port
(the one the gateway also defaults to, so the pair works together again in one
keystroke), and the menu says(changed — press V to keep it for next time)
while a setting is live but not yet written to the file.^Cleaves the
wrong-port notice — which swallows everything else you type, so a half-typed
line can no longer run menu commands by accident — aborts a transfer in
progress, and cancels at the filename prompt.Built by running the real period assembler (SLR
Z80ASM) underzxcc, so
SLR80 compatibility is structural rather than hoped for, with M80+L80 and ZMAC
as portability gates. Wrong choices are diagnosed rather than left to hang: a
port that never accepts a byte is named and explained, HBIOS is refused on a
machine with noRST 8vector, and the Z180 ASCI family is refused on a
processor that is not a Z180 (MLTtells them apart). Two paths cannot be
exercised by any test here and are documented as reasoned-not-run: the Z180
ASCI driver (our Z80 core has noIN0/OUT0) and the CP/M 3 console path.- Virtual modem — RomWBW HBIOS access (
hbios_1/hbios_2). Some CP/M
comms software doesn't drive a UART at all: software built for RomWBW asks
the firmware to move the byte, issuing anRST 8with a function number in
Band a serial unit inC. On a port profile such a program hangs before
printing anything — its first call goes nowhere — which no port address can
fix; RomWBW-targeted builds of period comms software are exactly this
case. Two newcpm_emu_uartchoices answer that
API for one serial unit (the virtual modem), so those builds now run:AT→
OK,ATDT host:port→CONNECT, with data flowing both ways. TheRST 8
vector is installed only for these profiles, so every other setting keeps
the untouched page zero of a plain CP/M 2.2 machine; a call for a unit other
than the selected one is refused, so a mismatch fails the same recognisable
way a wrong port address does. Implemented: the serial (character device)
group — in, out, input/output status, initialise, query, describe — plus the
version and serial-unit-count calls. Refused with an error result: bank
switching / memory management, disk, RTC, video, sound, DSKY (RomWBW
hardware services with no counterpart here) — an honest failure at the
call beats stranding a program later. A blocking call whose device isn't
ready parks the guest on the trap and is re-reported after the driver's
seam work, so it stays interruptible by the double-ESCbreak-out and
doesn't spin the host. Written from the published HBIOS interface
description (function numbers and register conventions); no RomWBW code is
included. Newsrc/cpm/hbios.rswith 11 unit tests, plus a fidelity fix the
guests depend on: an HBIOS return now sets the flags from the result byte
(asOR Aleaves them) instead of leaving the guest's stale flags. A guest
that hands the status straight to aJR Zwas reading our stale flags as
"transmitter not ready" and waiting forever. - Virtual modem — dialable as
CPM@<ip>(inbound). The CP/M emulator is
now a third dialable peer endpoint namedCPM, alongside Ports A/B: from
another modem on the gateway,ATD CPM@<ip>rings it exactly as
A@<ip>/B@<ip>ring the serial ports. The running CP/M comms program
seesRINGand answers withATA(or auto-answers afterATS0=n
rings), then the machines are joined transparently. Implemented additively
with a parallel global call slot and aCPM@hostdial parser — the A/B
peer-dial slots and routing are untouched (208 serial tests still pass).
Gated byallow_peer_dial; the endpoint answers while a comms program is
running (that's when the ring is polled). - Virtual modem — reachable over the master/slave relay. A device on a
slave gateway can dialCPM@<master-ip>: the slave relays the address to
the master, whose relay peer-dial handler resolves it to its own local
CP/M endpoint (the CP/M analog of resolvingA@/B@to a local port), so
CP/M running on the master is reachable from every attached machine. Both
directions were verified end-to-end over a live gateway: a.COMdialing
a TCP host viaATDT(CONNECT + data round-trip), and an external modem
dialingCPM@<ip>(the CP/M program rang, auto-answered, and received the
caller's data). - Virtual modem — slave-hosted CP/M reachable via the crossbar. CP/M
running on a slave is now dialable asCPM@<slave-ip>from the master
(or another slave) exactly as its A/B ports are: while a modem CP/M shell
is active the slave registers the labelCPMwith the master
(serial-register CPM) and, on a peer-dial claim, rings its own local
endpoint and bridges.parse_remote_peer_addraccepts theCPMlabel; a
master/standaloneATD CPM@<slave-ip>claims it through the crossbar; and
an async slave-side announcer (the CP/M analog of the physical-port
modem_slave_announce_tick, tied to the shell's lifetime) does the
registration. SoCPM@<ip>now works whereverA@<ip>/B@<ip>do.
Additive — A/B and the existing relay are unchanged. - Out-of-band break-out; remote outbound dialing. A double-
ESCnow
returns toA>at any time — not just at a console prompt but also from a
compute-bound program that never reads the console (the gateway watches the
wire out-of-band between CPU bursts), so a runaway no longer has to run out
the instruction ceiling. The CP/M-System banner shows "Press ESC twice to
stop a program." The CP/M modem can also dial a serial port on another
gateway —ATD A@<remote-ip>/B@<remote-ip>routes via the master/slave
relay (same routing andallow_peer_dialgate as the physical modem);
previously only the gateway's own ports were reachable. The CP/M modem's
peer-dial is now gated byallow_peer_diallike the physical modem. - Virtual modem — fidelity polish. Five additive enhancements to the CP/M
modem, none of which change the working polled path: (A) the AT command
layer parses a chained init string (ATE0Q0V1X4S0=1) and applies each
clause — echo, quiet, verbose/numeric andX-level result codes,
S-register set/query (S0auto-answer,S7peer-dial carrier wait),&C/&D,
ATZ/AT&Freset,ATI— instead of matching a few fixed strings; (B)
carrier is surfaced to the guest as the UART's DCD bit (SIO RR0 bit3, 6850
bit2), active-high so the idle status byte is unchanged; (C) flow control
both ways — the UART reports transmit-not-ready when the TX ring is full and
the peer read is capped to the guest RX ring's free space, so a speed
mismatch back-pressures instead of dropping bytes; (D) a fuller Z80 SIO
register model (WR0 read-pointer +RR1/RR2), a strict superset of the
RR0-only behaviour; (E)CPM@<ip>is now an answer pool — every
modem-enabled CP/M session can answer the next inbound call (a hunt group),
with one session owning the slave→master crossbar announcement. - ADM-3A terminal translation. The emulator presents CP/M programs with
a Lear Siegler ADM-3A terminal and translates its screen-control stream to
the connected client: ANSI cursor sequences for a modern terminal, native
cursor codes for a Commodore 64 (PETSCII), best-effort for a dumb ASCII
TTY. Client arrow keys are translated the other way into the ADM-3A cursor
codes the program reads. This lets full-screen software (WordStar, Turbo
Pascal, editors) installed for an ADM-3A render correctly. The decoder and
per-terminal renderers are a self-contained, unit-tested module. - Configurable runaway ceiling. A new config key
cpm_emu_max_minstr
(millions of Z80 instructions per program run, default 2000 = 2 billion;
wired into the telnet, web, and GUI config UIs) bounds a compute-bound
.COMthat never reads the console, so theA>prompt always returns.
Interactive programs remain escapable with a double-ESCat any input
prompt. In the GUI and web UIs the CP/M controls (enable + ceiling) moved
into the "AI, Browser & Weather — More" panel to keep the main screen
uncluttered; in the telnet UI they live in a CP/M submenu under Other
Settings →E. - Free-TPA report. The boot banner and
VERnow print the size of the
transient program area (63K TPA free (0100-FDFF)), the way a real CP/M
system reports its memory on cold start, so a user can see how much room a
.COMactually gets. Derived from the emulator's own TPA constants.
- Virtual modem — RomWBW HBIOS access (
-
Gateway Shell: three new commands.
CLS/CLEARclears the screen;
VER/VERSIONprints the shell identity and gateway version; and
FIND <pattern>/WHERErecursively searches all of drive A: (not just the
current directory) for files whose name matches a wildcard, printing each
hit's A: path. TheFINDwalk is bounded (scan and result caps) and never
follows symlinks, so it stays inside the transfer-directory jail. -
First-run setup wizard (desktop GUI). A fresh install now opens the GUI
window into a nine-screen wizard instead of a configuration editor the
operator has to reverse-engineer: credentials (typed twice, with a
show-password toggle and the cleartext-storage warning), telnet, SSH, access
control, the web server, the transfer directory, the CP/M emulator and its
virtual modem, and the gateway role — with a master-connection screen on the
slave path. It closes with a review listing what will be saved, the actual
commands to connect with, and the inbound TCP ports to allow on the
firewall. Beyond password-match it validates port syntax and refuses a port
another listener already claims (including the standalone Kermit server it
never asks about), and warns about sub-1024 ports and about an unauthenticated
gateway with IP safety off. Choosing the Master role arms
master_accept_relaysbut never silently enables the SSH server the relay
needs: the role screen explains why a master wants it and offers a button, and
the review screen warns if it is still off. The wizard edits a draft, so
nothing reaches the config or the running server until its final Save and
Restart Server; exiting or skipping writes exactly one key. Re-runnable from
Server — More… → Run setup wizard…. It is deliberately GUI-only — telnet and
the web UI already expose every key it touches. New key
setup_wizard_completed, asymmetric on purpose:falsefor a config the
gateway creates itself (a fresh install sees the wizard), but a config file
that lacks the key reads astrue, so an upgrade is never dropped into it. -
A loud warning when the listeners don't come up. Each listener already
logged its own bind failure, but one line in the startup chatter is easy to
miss and the process kept running afterwards, quietly serving nothing — the
failure mode being a second copy of the gateway started without stopping the
first, where the old process holds the ports and everything you connect to is
still served by the old binary. It looks exactly like "my settings changed
nothing".src/bindwatch.rsnow collects each listener's outcome and, once
they have all reported, says so: NONE of the N configured listener(s) could
bind, which ports, that the process is serving nothing, that another copy is
almost certainly holding them, and the commands to find and stop it
(pgrep/ss/pkill, ornetstat/Task Manager on Windows). A partial
failure gets a shorter note; a total failure that is not address-in-use
(e.g. ports below 1024 without root) warns without blaming a second copy; a
serial-only setup with no listeners stays quiet. -
The SSH server binds its own socket (
run_on_socketrather than
run_on_address, which is literally bind-then-run_on_socket). A bind failure
is now reported as one: the old form logged "SSH server listening on port
2222" first and then surfaced "Address already in use" as a generic
post-hoc "SSH server error", which read as though the port had come up. -
A slave's modem ports now register with the master at startup, so the
master can ring them. The registration machinery existed but was gated
behindallow_peer_dial, which meant a slave with the default settings could
never be reached from its own master — the master's Serial Gateway menu
simply never listed the port, and nothing contacted the master until the
attached device happened to dial out. That gate was the wrong one:
allow_peer_dialgoverns this gateway dialing arbitrary peers, whereas
master/slave is already an explicit, mutual, authenticated pairing (the slave
holds the master's credentials; the master setsmaster_accept_relays). A
third party reaching the port through the master's crossbar is still gated, on
the master. The announcer's log lines now describe registration rather than
peer-dial. -
The slave's CP/M endpoint is registered for the server's lifetime, the way
Ports A and B are — not only while someone happens to have the emulator open.
It used to announce per CP/M session, soCPM@<slave-ip>blinked in and out of
the master's Serial Gateway list and the master could not see that the endpoint
existed at all. A call arriving with no session running rings the answer pool,
finds it empty and is reported unanswered — exactly like dialing a modem port
whose device is switched off. Declines to register when the emulator is off or
its virtual modem isoff, since nothing could ever answer. -
The slave-link summary stopped giving a stale reason. It printed
CPM emulator not announced (needs allow_peer_dial)— true when the
announcer was gated on that flag, and after the fix below merely misleading: it
sent an operator hunting for a setting to change. It now names the real reason. -
The router-block fallback is per address family. The
x.x.x.1fallback
keyed off "no router detected at all", so a host where only an IPv6 default
route was found would stop applying it to IPv4 — quietly weaker than the
operator asked for, since the IPv4 router was still unknown. It now keys off
whether that family's router is known. -
Two config keys were in no documentation table:
allow_peer_dial(a
security-relevant one) andkermit_wait_for_receiver, though Appendix A claims
to list every key the parser recognises. Both are documented now, along with
the auto-managedgui_window_geometry, andallow_peer_dial's description
states what it does not gate (registration). -
The slave's CP/M endpoint registers with the master too. Same wrong gate
as the serial ports:cpm_slave_announceand its spawn site both required
allow_peer_dial, so on a default slave theCPMendpoint never announced and
CPM@<slave-ip>was unreachable from the master. Registration is now
automatic whenever the role isslaveand a master host is set (the CP/M
guest dialing out to a peer port stays gated, incpm_modem.rs), and it
re-announces on a master-settings change like the ports do. -
A config change re-registers both slave ports. A registration is a
standing claim — the master holds an idle channel and rings it later — so one
made under settings that have since changed is worse than none. Both the
modem announcer and the console register loop now watch a fingerprint of what
a registration depends on (role, the master's host/port/username/password, and
that port's own enabled flag, mode and device path) and re-register within
about a second of any of it changing, whichever UI made the change. Unrelated
edits — a weather location, a timeout — deliberately don't disturb a live
registration, and a call in progress is never interrupted (the check only runs
while idle).
Changed
-
Gateway Shell now surfaces the CP/M "destination first" operand order.
COPYandMOVEtake the destination before the source (COPY dst src) —
the reverse of the order most users expect today. The shell now prints two
reminder examples on entry, and a failingCOPY/MOVE(e.g. "File not
found." after the operands were swapped) echoes the correct form
(e.g. COPY dst src (dest first)) so the mistake is self-correcting. -
CP/M settings are named where they live, and the way out is spelled out.
The GUI frame + its "More" popup and the web card + its modal that hold the
CP/M enable toggle, runaway ceiling, and virtual-modem port are now titled
"AI Chat, Browser, Weather & CP/M" (and "… & CP/M — More"), so the CP/M
settings are discoverable rather than hidden behind an AI/Browser/Weather
label. The emulator's entry banner now shows a prominent "Type EXIT to return
to the gateway." line beside the "Press ESC twice to stop a program." hint. -
The CP/M virtual modem is documented as polled-only. The emulated UART is
polled (the guest reads the status register for RX/TX readiness); the core
never raises a serial interrupt in any Z80 interrupt mode. This holds for
every port profile — the family (Z80 SIO / 6850 ACIA / 8080 88-SIO) only
selects the I/O port address and status-bit layout, not interrupt support —
so polled comms software works on any profile while interrupt-driven serial
software is unsupported. Noted in the manual and theuartmodule. -
The CP/M emulator and its virtual modem are on by default.
cpm_emu_enabledwas default-off while the emulator was being built out; it is
on now because the feature is bounded (guest jailed totransfer_dir/CPM,
runaway stopped bycpm_emu_max_minstr, always escapable with a double-ESC,
no path to a host command) and it ships with a terminal of its own. The
virtual modem now defaults torc2014_1b— the port EGT80 expects — so the
emulator and its terminal work together untouched. Both remain settable:
cpm_emu_enabled = falsefor no guest code at all,cpm_emu_uart = offfor an
emulator with no modem (guest code can dial out when a port is selected). -
A one-click “Default port” for the CP/M virtual modem in all three UIs
(telnetDin the CP/M panel, a button in the web CP/M section, a button
beside the GUI combo): resetscpm_emu_uartto the default whatever it was
showing — the answer to “I changed something and now the terminal cannot
connect”. Guarded by a test that fails if EGT80's own default port and the
gateway's default profile ever drift apart, since that pairing is the whole
point and neither build would otherwise notice. -
“Block connections from the router” (
disable_gateway_connections) in all
three UIs, on the row under the login checkmarks. Off by default, which
changes behaviour: a connection from the local router is now allowed while
the IP allowlist stays in force. It used to be refused outright, which left
disable_ip_safety(dropping the allowlist entirely) as an operator's only way
in; the narrow rule is now the opt-in. Loopback is exempt either way, public
addresses are still refused, and the toggle applies on the next connection with
no restart.- The router's address is queried from the OS, not guessed. Earlier work
assumed the router wasx.x.x.1— a convention, not a fact, which both
missed a router living on.254and refused an ordinary machine that
happened to sit on.1.src/router.rsreads the default route's next hop
(Linux:/proc/net/route+/proc/net/ipv6_routedirectly, no subprocess;
macOS/BSD:route -n get default; Windows:route print, matched on the
addresses rather than the localised column headings) and the rule blocks
exactly that, IPv4 and IPv6, including both routers on a multi-homed host.
The query runs on a background thread at startup and re-runs when the cached
answer ages past five minutes, so a DHCP change is picked up without a
restart; it is never run on the connection path, which only reads the
cache. The detected address is logged at startup and named in the checkbox
label in every UI — "Block connections from the router (192.168.1.1)". Where
it cannot be determined the rule falls back to thex.x.x.1convention (per
address family), so it is never silently weaker; loopback stays exempt, and
no detected address can widen the allowlist. Every parser is a pure function
tested from captured output on all three platforms, including a localised
(German) Windows route table.
- The router's address is queried from the OS, not guessed. Earlier work
-
A slave's log now tells the whole story. Each attempt to reach the master
is logged with its number (announcing to master 10.1.2.3:2223 as 'relay' (attempt 5)), so a slave that cannot connect no longer looks identical to one
sitting idle — previously the failure reason was deduped (rightly, it repeats
forever) and nothing showed the retries still happening. On success there is a
plainCONNECTED to master …line followed by one consolidated block naming
every port, the mode it is in, and what the link is doing:Slave link to master 10.1.2.3:2223 — Port A mode=modem registered — awaiting a pick from the master Port B mode=kermit bridging — a master user is attached CPM emulator announced — dialable as CPM@this-hostDisabled ports are listed as disabled rather than omitted, because "why is
port B missing?" is exactly what a summary should answer. The CP/M announcer
also gained what the serial loops already had: its failure reason is now
logged (it used to be discarded, leaving identical lines with nothing to act
on) and its retry backs off 1s→30s instead of hammering a dead master once a
second. -
EGT80 v0.7 — the screen is cleared, and everything configurable is under
Settings. The screen is cleared and the "Ethernet Gateway Terminal" banner
redrawn before the main menu and on entering terminal mode, so it stays clear
which program you are talking to once a remote system has filled the screen;
the terminal-mode banner now also names the menu key and says that the key
followed byEreturns to the main menu. CP/M has no standard clear-screen, so
a new Settings item picks the dialect: ANSIESC [ 2 J(the default —
what a terminal emulator over USB serial and the gateway's own ANSI clients
understand), ADM-3A^Z(a period terminal, or a PETSCII C64 through the
gateway, whose translation re-renders it), or off for a printing terminal.^Z
was the first default, on the reasoning that its failure mode is silent rather
than[2Jprinted as litter; it moved to ANSI because silent-failure is what a
modern terminal on real CP/M hardware actually got. Because a clear can wipe a message before it is read, the
places where a message is the only feedback — a damaged settings block, a save,
a refused port family — now pause for a keypress.The serial-port selector moved off the main menu into Settings, which now
shows the port, the menu key and the clear dialect alongside the filter mode.
The menu key is any control key you press rather than a cycle through three
fixed choices, because which key is free depends on the remote system (^Yis
WordStar's delete-line,^]telnet's escape,^\Kermit's). Five keys are
refused with the reason:^Cbacks out of every screen, CR/LF/TAB are ordinary
typing, andESCbegins the arrow-key sequences, so a cursor key would open the
menu. A saved key is validated at startup too, since an invalid one would trap
that key for ever.- A wrong HBIOS unit froze EGT80 instead of reporting a dead port.
Reported from an SC126: with the wrong port selected, the "nothing was sent"
notice appeared as designed — and then^Cdid nothing and the machine had
to be reset. The published API is explicit thatCIOIST/CIOOSTreturn a
count inAand that "negative values (bit 7 set) indicate a standard HBIOS
result (error) code". Under EGT80's vector contract, non-zero means ready, so
an error read as "a character is waiting"; the driver then calledCIOIN,
which the API says "will wait indefinitely". The terminal loop never got back
to the keyboard, so the menu key and^Cwere both dead. Those two status
calls now treat a negative result as not-ready, which turns the hang into the
diagnosis EGT80 already had: the transmitter never comes free,PSENDgives
up, and the wrong-port notice appears with^Clive. The emulator's own
CIOIST/CIOOSTnow cap their counts at0x7Ffor the same reason — a
count with bit 7 set would read as a failure on a healthy port. - A refused AT command is logged verbatim, and debris before
ATis
skipped.ATDT ethernetgatewayintermittently answeredERRORfrom an
SC126 in two different terminal programs, working when retyped. The new log
line caught it on the first occurrence:refused command "CCatdt ethernetgateway" [43 43 61 74 …]— two XMODEMChandshake bytes, left in
flight by a download that stopped early, arriving as the first characters of
the next command line. Nothing legitimate precedesATon a command line, so
the command is now found rather than refused, with the skipped bytes logged.
A real modem would answerERROR; being stricter than the hardware here buys
nothing and costs the user an unexplainable intermittent fault. - Transfers were silently corrupted on the HBIOS and
AUX:drivers.
Reported from an SC126: a downloaded file came back the correct length and
entirely zero, with no error at either end. The cause is register
discipline. The console entry points (CST/CIN) have always savedBC,
DEandHLaround the driver; the port entry points (PST/PIN/
POST/POUT) were bare jumps that saved nothing. Three drivers touch only
AandBC, so callers got away with holding a pointer inHLacross a
port call — but HBIOS is anRST 8into RomWBW's firmware andAUX:is a
BDOS call, and neither promises to preserve anything beyond its documented
returns (real RomWBW returns values inHLfor several functions).
Both XMODEM inner loops walk the buffer withHLacross exactly those
calls, and the failure is quiet and total: on the send side the byte
transmitted and the byte folded into the CRC both come from the wandering
pointer, so they agree, the receiver's check passes, and a file of exactly
the right length arrives full of whateverHLhad wandered onto. On the
receive side the bytes are stored through the wandering pointer while the CRC
is computed on the byte inA, so again every block "passes". The four port
entry points now save and restoreBC/DE/HLexactly as the console ones
do. - The emulator's HBIOS no longer preserves
HLeither. This bug could not
be reproduced here because ourRST 8only set the registers the API
documents, soHLsurvived — an emulator looser than the hardware turns a
reproducible bug into a field report. The character-I/O and management calls
now scrambleHL(CIOQUERYandCIODEVICEare exempt: they returnL).
Verified afterwards with an independent XMODEM peer over TCP: a download and
an upload on the port drivers are both byte-identical. - The post-transfer notice no longer costs a keystroke, and the hangup
always finishes. Two faults reported from an SC126, both introduced by
earlier fixes in this same area. The "press a key to return to the session"
prompt collided with the far end's own prompt whenever that survived the
line settle, so one event cost two keystrokes and the first appeared to do
nothing but move the cursor down a line. It is now a statement rather than a
prompt —Back in the session - press a key if the far end is waiting for one.— and the far end owns the keystroke it asked for. And the hangup's
settle waited for silence that a talkative peer need never provide, so
Hanging up...could be the last thing the program printed and the machine
had to be restarted; it is now bounded to three drains and abandonable with
^C, because an exit path has to terminate whatever the other end does. - EGT80 hangs up when it exits. Leaving the program used to leave the call
up: the gateway held the session open, and on a real phone line it would hold
the line. There is no DTR to drop — the SIO and ACIA drivers do not touch the
modem-control bits, HBIOS does not expose them, and CP/MAUX:has no notion
of them — so the modem is asked, with the Hayes escape. That means honouring
the guard time, and the guard is a duration, not an instruction count: the
settle runs four timeout passes rather than one, because a poll loop that
takes a second on a 4 MHz Z80 takes a fifth of that on an 18 MHz Z180, and an
escape sent too early is not an escape — it is three plus signs typed at the
far end. If there is no modem at all (a null-modem cable to another machine)
the far end receives the characters as text; that is the price of not being
able to tell, and it is smaller than walking away from a live call. - The virtual modem no longer forwards the
+++escape to the peer. A real
modem swallows those characters and only sends them on if the sequence turns
out not to be an escape — and this gateway's physical modem already did
exactly that. The CP/M one forwarded them as they arrived, so every peer saw
+++whenever a guest hung up, which a guest that hangs up on exit makes
every session. Held characters are flushed in order if the run breaks, so
nothing is lost; a+inside a data stream is still ordinary data, protected
by the same guard as before. - After an in-session transfer, EGT80 says a key is needed. Settling the
line throws away whatever the far end said as the transfer ended — that burst
is where a truncated escape sequence comes from, and a BBS's "press any key"
lives in the same burst. The result was a screen showingReceived.and then
silence, with nothing to explain what to do, which reads as a hang. Both
directions now end withPress a key to return to the session.and wait for
one key. The key is not forwarded: the far end may not be waiting for
anything, and a stray byte pushed into someone's menu is worse than pressing
a key twice. - The line is settled after a transfer, so a lost
ESCcannot print as
litter. Reported from an SC126: after a download the screen showed2J
and did not clear — on a terminal that is definitely ANSI. The cause is the
boundary, not the terminal. The far end starts talking the moment the last
block is acknowledged (a BBS prints its own prompt, then redraws its menu)
while this end is still closing the file — a BDOS close is disk I/O — and a
polled UART holds exactly one byte, so those first bytes are overwritten and
lost.ESC [ 2 Jarriving without its first two bytes is the text2J.
EGT80 already had a line-purge routine, used before a receive and after a bad
block but never at the end of a transfer; it now settles the line on every
transfer exit, in both directions and from both the menu and terminal mode,
and clears any half-seen escape sequence out of the filter so the session
resumes on a boundary the far end will send whole. - A transfer inside a session no longer asks for a key. Reported from real
use: after a download EGT80 said "Received.", waited for a key, and then
something asked again. The second prompt was the far end's — the gateway's
own File Transfer menu ends with "Press any key to continue." — so two
programs each wanted a keystroke, back to back, which reads as a bug however
it is explained. Every EGT80 transfer exit path was checked: none of them
waits internally, the wait was in the caller. A transfer started from inside
terminal mode now prints its result and returns straight to the session,
which is what period terminals do and what a BBS expects: the BBS's own
prompt and menu redraw follow naturally, and nothing is lost because
returning to a session clears nothing. A transfer started from EGT80's own
menu still waits for one key, because the menu redraw clears the screen and
the result would otherwise vanish. - Coloured menus, and a terminal menu that explains itself. Headings,
labels, values and the key letter of every menu line are now coloured.
Colour follows the ANSI/ASCII setting already in Settings — that setting
means exactly what colour depends on — so ASCII mode emits not one escape
byte and a printing terminal or PETSCII console sees the plain text it always
did. The key letters are coloured by a printer that recognises the shape
every menu line already has (two spaces, key, two spaces, text), so the menus
remain single blocks of text in the source and continuation lines are left
alone; the escape sequences live in their own strings so the screen-fit test
still measures printable width. The terminal-mode menu no longer says
(twice=send), which told the user nothing: it now reads
Menu: E)xit H)elp S)ettings U)pload D)ownload/
^Y again sends ^Y itself. Choice:, naming the actual key both times. - Assembly quality pass. A mechanical sweep of
EGT80.Z80found and fixed:
two source lines over the 80-column house limit;HBCNTwritten but never
called (the HBIOS unit list scanned a fixed 0–3 instead of asking how many
units exist — it now asks, lists only those, and refuses a unit the firmware
does not list); an orphaned label left behind when the detection hint replaced
the older notice; and two label pairs that collide within their first six
characters —MASCHB/MASCHB2(new) andXREADY/XREADY1(latent since
the transfer code was written). The six-character rule is in the editing rules
because a stricter assembler may treat such a pair as one label and merge
them silently; both are renamed. AlsoSHRATEfell through to the baud table
for the 88-SIO, printing a rate for a chip with no rate register — every
switch on the port family was audited for the new driver. Screen conventions
are now uniform: every full screen clears first, with the one deliberate
exception commented (the ASCI menu must not clear, or it would wipe the base
it just reported). - The port menu names machines rather than chips. Choosing a port meant
choosing a chip family first, which is a question only someone who already
knows their board can answer — and two entries claimed an SC126 (Z180 ASCI
and RomWBW HBIOS), with only one of them able to work. The top level now
reads: the gateway's own emulated port (option 1, and the shipped default, so
EGT80 and the gateway work together with nothing set), RomWBW firmware, the
Altair 88-2SIO, the Altair 88-SIO, "other hardware", and CP/MAUX:. The old
chip list survives intact one level down, free-form address entry included, so
nothing became unreachable. A line above the list states which key follows
from what the program can determine about the machine — RomWBW present, a Z180
without it, or neither — which is as far as detection can honestly go, since
probing an unmapped port on real hardware is not something to do blind. - New driver: the Altair 88-SIO (the original MITS board, 0x00/0x01 or any
address), as its own menu item. It is a different board from the 88-2SIO, not
just a different address: it reports ready by pulling a bit low, so a 6850
driver pointed at it reads every test inverted and the port appears
permanently busy and permanently empty at the same time. Verified in the
emulator against thealtair_sioprofile:AT→OK,ATIidentifying the
modem. - Z180 ASCI reaches the port the way the machine actually works. Reported
from an SC126:atdtproduced no error but a few random characters, while
other comms software on the same wire was fine. The reason is not addressing — it is
ownership. RomWBW's SC126 build enables its ASCI driver with interrupts in
mode 2, so an interrupt handler is draining the receiver into the firmware's
own buffer; a program polling the same registers races that handler, which
reads the data register first. The transmitter still accepts bytes, hence no
error. This is exactly why RomWBW-targeted builds of such software exist.
Choosing a channel under option 4 therefore now asks the firmware whether it
serves that channel, and if it does, reaches it through HBIOS the way
RomWBW-targeted software does — saying so, and naming the unit. Direct register
access remains for a Z180 the firmware is not driving, where it is the only
way in. The port menu also states when RomWBW is detected, so a user need not
know which of the two options applies to their machine. - Setting the ASCI I/O base no longer looks like selecting the port.
Reported from real use: choose Z180 ASCI, set the base, back out — and the
port menu still showed the old port, because the family is only selected
when a channel is chosen. The base is now filled in from RomWBW on the way
into that menu (so on a RomWBW machine it is not a step at all), the menu
shows the base in force and says nothing is selected until a channel is
picked, both base setters end with "now pick the channel", andQsays it
leaves the port as it is. - The opening menu names the menu key, so terminal mode is visibly not a
one-way door before you enter it:Keys: ^Y leaves terminal mode for the menu; then E returns here.The Settings item for the clear-screen dialect is
now justClear Screen. - On a RomWBW machine EGT80 asks the firmware instead of guessing. The
HBIOS unit prompt now lists the character units that actually exist, each
with the device type and base I/O address RomWBW reports for it
(CIOCNT+CIODEVICE, both published calls), so "which unit?" is read off
the screen rather than guessed; a unit that does not exist says so. The Z180
ASCI menu gainedR, which takes the register base from that same answer —
necessary becauseC0is only mostly right (RomWBW usesC0on the Small
Computer, RC2014-Z180, SZ180, GMZ180, DYNO and EPITX platforms, but40on
N8, MK4, N8PC and RPH). The reported base belongs to a physical channel and
channel 1 sits one address above channel 0, so the channel is subtracted to
recover the block base. - The emulator's HBIOS no longer claims to be a 16C550.
CIODEVICE
reported device type0x00on the reasoning that zero meant "no driver" —
but0x00isCIODEV_UARTin the published list, so the answer was a
definite claim to be a chip we are not. Nothing noticed until EGT80 began
displaying that field and the virtual modem listed itself as
UART base 00. It now reports a type outside the published range, which
decodes as "not one of the known drivers" — the truth for a TCP connection. - The Z180 ASCI menu offers the RomWBW I/O base by name. The Z180's serial
registers live inside the CPU and the register block is relocatable; RomWBW
puts it atC0on Small Computer Central boards, so EGT80's default of00
addressed nothing there and failed the way that family always fails —
silently.Cnow setsC0with the boards named on the menu,Bstill
takes any base by hand, and the help says which is which. (On a RomWBW
machine the HBIOS family sidesteps the question entirely: the firmware knows
where its own registers are. That remains the recommended path, and the one
RomWBW-targeted builds use.) - Every EGT80 screen is now checked to fit a 24×80 terminal, by a new test
that parses theDBstrings out ofEGT80.Z80— a source-level check, so it
runs in CI even though the binary itself cannot be rebuilt there. It found
help page 3 had been two rows over since it was written and page 2 going over
as line settings were described in it, both of which pushed the page's own
heading off the top; both are trimmed, and each help page now clears the
screen instead of scrolling on below the previous one. - Line settings (baud rate, data bits, parity, stop bits) on a new Settings
submenu, applied where a terminal genuinely can and refused with an
explanation where it cannot. RomWBW HBIOS takes speed and framing in one
CIOINITcall — the published line-characteristics word, whose baud field is
an exponent pair (V = 75 × 2^X × 3^Y, bitsYXXXX) rather than a rate, with
RTS and DTR asserted deliberately because clearing DTR on a real modem drops
the call. Z180 ASCI sets the rate and framing in CNTLA/CNTLB by
read-modify-write, preserving the receiver and transmitter enables (reasoned,
not run: our Z80 core has noIN0/OUT0). A 6850 ACIA sets framing and
the ÷1/÷16/÷64 clock divider; the eight combinations it lacks (7 data bits
without parity, 8 with parity and two stop bits) are refused by name rather
than rounded to the nearest, because a framing mismatch presents as garbage
characters. A Z80 SIO/2 has no baud generator at all — the bit rate comes
from the board's clock or CTC and its registers are write-only, so EGT80
declines rather than reprogram from a guess — and CP/MAUX:belongs to the
OS. Against the gateway itself none of it applies in either direction: a TCP
connection has no bit rate, so the emulated UART accepts line-configuration
writes and ignores them. The default is therefore program nothing at all,
withRto return to it: the port keeps whatever the ROM, firmware or OS set
up. Applying makes it stick — it is re-applied whenever the port is selected,
including at startup, guarded so a config carried to the wrong machine cannot
RST 8orIN0on hardware that has neither. Verified end to end in the
emulator: after applying 19200 7E2 through HBIOS, aCIOQUERYprobe read back
289E, exactly that framing plus RTS+DTR. The four ASCI register operands
are patched byASCPATalong with every other one in that family — a review
caught them missing, which would have sent the CNTLB writes to CNTLA, whose
receiver/transmitter enables would then have silenced the port.
- A wrong HBIOS unit froze EGT80 instead of reporting a dead port.
-
EGT80:
^Cbacks out of every menu, not just the notice screens — the port
family list, all four per-family prompts, and Settings — so one habit works
everywhere.Qstill does the same, and the menus say so.
Fixed
ATDT ethernetgatewaynow works from inside CP/M. The physical serial
modem has always answered the gateway's own dial targets — the keywords
ethernetgateway/ethernet-gateway/ethernet gatewayand the built-in
number1001000— but the CP/M emulator's virtual modem knew nothing about
them: the keyword fell through to the TCP path, failed to parse as
host:port, and came backNO CARRIER. Dialing it from EGT80 now spawns a
session on this gateway's own menu over an in-memory duplex, exactly as the
physical modem does, with raw serial semantics (no telnet IAC negotiation,
whose bytes would reach the guest as garbage). Two more parity gaps went with
it: a bare hostname no longer needs an explicit port (a target with no
:portdefaults to telnet's 23, soATDT bbs.example.comdials instead of
failing), and a phone number is looked up in the dialup phonebook as on
the physical modem.- The CP/M virtual modem's AT settings now persist, as the physical ports'
always have.AT&Wwas swallowed by the command parser's catch-all: it
answeredOKhaving stored nothing, and the modem was rebuilt at factory
defaults on every entry to the emulator, so a comms program's init string had
to be retyped each time.AT&Wnow writes the profile — echo, verbose, quiet,
result-code level, DCD mode and all 28 S-registers — to newcpm_emu_*keys,
and the modem powers up with it.ATZrestores the saved profile rather than
the factory one, matching both a real modem and this gateway's serial ports,
whileAT&Fremains the command that ignores it. Values are clamped and an
unparsable S-register list falls back to the power-on registers, so a
hand-edited file cannot produce a state the AT layer never would. The profile
is editable in the web and GUI config editors beside the other CP/M settings —
the same treatment the ports'AT&Wfields get, and for the same reason.
Verified end to end:ATE0 S0=3 S7=20 &C0thenAT&W, gateway restarted, and
the emulator came back with echo off,S0=3,S7=20. ATD <host>no longer eats the first letter of the hostname. The CP/M
modem stripped a leading tone/pulse modifier after trimming, so any host
beginning withTorPlost that letter when dialled without a modifier:
ATD telnetbible.comsilently dialledelnetbible.comand reported NO
CARRIER. AT/Pis now only a modifier where it sits against theD
(ATDT host), which is how the physical modem has always behaved — it honours
modifiers only inside a phone-like string. Found reviewing the dial path, and
confirmed against a real BBS.- Backspace works at the CP/M modem's
ATprompt. The byte was removed
from the command line, but the echo was the rawBS/DEL— a bareBSonly
walks the cursor left and leaves the character on the screen, and EGT80's
filter dropsDELoutright as printing-terminal litter. Either way the line
looked uneditable, so a typo could only be fixed by starting again. The echo
is now a destructive erase (BS SPACE BS), and nothing is echoed when the
line is already empty, as a real modem does. - The CP/M virtual modem no longer chokes on
CR NULline endings. An NVT
telnet client writes a bare Return asCR NUL(RFC 854). TheCRended the
command correctly, but theNULstayed in the modem's line buffer and became
the first character of the next command — so it no longer began withATand
came backERROR. The first command of a session worked and every one after it
failed, which made a parsing bug look like a dialling problem: a correct
ATDT host:portwas refused while the identical first attempt had been
accepted.NULis now ignored exactly asLFalready was, which is what a
real modem does with padding. Found from a user's screen, not from the tests —
the test driver sent a bareCR, so nothing exercised the case; the CR-NUL
sequence is now a regression test. - Kermit refuses a binary file declared as text, even with no length given.
The length check added earlier can only fire when the peer declares a size,
and the CP/M clients that hit this in practice do not: some send the file
type but no length, and others send no attribute packets at all. A peer
that declares TEXT mode and then sends bytes plain text never contains (NUL,
and the other C0 codes that are notBEL/BS/TAB/LF/VT/FF/CR/ESC
/^Z) is sending a binary file that stopped at the first^Z, so the upload
is now refused in-band on the content alone. The test is deliberately narrow —
ANSI art, tabs, a trailing^Zand high-bit text (WordStar, PETSCII, UTF-8)
are all still accepted as text. A peer that declares nothing cannot be
distinguished from a legitimate binary upload, so that case is logged with a
warning rather than refused. - The CP/M emulator no longer reports a RomWBW system when none is
configured. TheRST 8vector is only installed for anhbios_*profile,
but the trap address itself is always live, so a guest that reached it another
way (aCALLstraight at it, or a stray jump) got a successfulVERreply on
a port profile — telling a program that probes before choosing how to reach
its modem exactly the wrong thing. Every HBIOS function now fails when no
HBIOS access mode is selected. - A CP/M program parked on a blocking HBIOS call now honours the session idle
timeout. The timeout lived only in the console read path; the parked path
polls the modem instead of blocking on the wire, so an abandoned session could
sit in a 2 ms poll loop indefinitely. Any progress or keystroke resets it, so
a program legitimately waiting for an inbound call is only closed when the
user has actually gone away. - Kermit uploads that arrive incomplete are no longer saved as if whole.
Three gaps let a corrupt upload reach disk silently — every packet's block
check passed, the sender said "end of file", and we wrote what we had:- Truncated files are now refused. The receiver compares the byte count
it collected against the length the sender declared in its attribute
packet; a short file gets an E-packet (so the peer's user sees the failure)
and is never committed. Arriving longer than declared stays legal — a
text-mode sender declares its on-disk size and then expands line ends on
the wire — and is logged, not refused. - A sender-abandoned file is now discarded, per spec §4.7. An EOF packet
carryingDmeans the sender gave up part-way through; we previously ACKed
it and kept the partial bytes, committing a truncated file. The record is
now dropped and the session continues, so the rest of a batch still lands. - A peer uploading in text mode is now called out in the log. When the
attribute packet declares ASCII/text file type, the receiver warns that
binary files (.COM, game data) will be corrupted and names the one-line
fix on the peer (SET FILE TYPE BINARY) — CP/M Kermit defaults to text
mode, where the sender stops at the first^Z.
- Truncated files are now refused. The receiver compares the byte count
- Kermit server no longer retains every uploaded file in memory for the
whole session. The server-mode dispatch loop now frees each received
file's payload as soon as theon_filehook has committed it to disk, so a
long-lived session on the always-on serial or standalone-TCP Kermit server
(both reachable without authentication) can't accumulate every upload's full
contents in memory across an unbounded number of transfers. Filenames and
metadata are still returned for the post-session summary; no behavior change
for callers (all committing already went throughon_file). - CP/M emulator — correctness/stability fixes from a full review of the new
emulator. None affect a released version (the emulator is new in 0.8.0):- Interactive programs no longer hang on console-status polling. BDOS 11
(console status) and BDOS 6 sub-function0xFEreported "no key ready"
even when a keystroke was already buffered, so the standard
LD C,11 / CALL 5 / OR A / JR Zpoll idiom spun until the instruction
ceiling — hanging full-screen / interactive.COMs. They now report a
buffered key (both are non-blocking); BDOS 6 direct console input
(E=0xFF) stays blocking (the common single-key /Y-Nidiom), with the
non-blocking poll served by the0xFEstatus call and BDOS 11. - A telnet
CR NULEnter no longer skips a launched program's first
prompt. A telnet client transmits a bare Enter as the NVT pairCR NUL;
the command-line reader consumed theCRbut left theNULqueued, so a
.COMlaunched from that line (e.g.CLRDIR B:) had its first console
read — often aY/Nconfirmation — satisfied by the strayNULand never
waited for the user. The line reader now also drains the trailingNUL
(andLF), so no terminator byte leaks to the next read. - A single
ESCat a program's line prompt no longer drops the session.
BDOS 10 (read-console-buffer) is now read through the same console path as
the other calls:CRterminates, backspace edits, and a double-ESC
aborts the program back toA>(a loneESCwas previously mistaken for a
disconnect, and the "ESC twice to stop" promise did nothing mid-line). - A BDOS call made via the
0x0006entry-address pointer is now serviced.
Only the0x0005entry was trapped, so a program that called the BDOS
address read from0x0006ran off into uninitialised memory. - The CP/M inbound-call request is cancel-safe. A
request_cpm_call
cancelled mid-wait (the slave announcer aborted on shell exit, or a dial
racing shutdown) no longer leaves a stale call in the endpoint slot — which,
with two or more concurrent CP/M sessions, could spuriously report BUSY to
real callers or "answer" a dead call. Reclaimed via an RAII guard mirroring
the A/B peer slot. - Existing files resolve case-insensitively. An operator-placed lowercase
host file (foo.txt) that appeared inDIRcan now actually be opened /
TYPEd / renamed, not just listed — CP/M's uppercase 8.3 name is matched
case-insensitively (new files are still created uppercase). +++escape guard time. The online+++escape now requires a
preceding idle gap (S12), so a+++inside a binary data stream is treated
as data instead of dropping the guest to command mode mid-transfer.- Altair 88-SIO honours transmit-not-ready. The
altair_sioprofile now
clears its TX-ready bit when the transmit ring is full, so the no-byte-loss
flow-control guarantee holds for it as it already did for the SIO / ACIA
profiles. STATnow reports real free space instead of 0 bytes. The disk-info
BDOS callsSTATuses — 31 (Get Addr(DPB)) and 27 (Get Addr(Alloc)) —
were unimplemented and returned 0, soSTATread a garbage disk-parameter
block / allocation vector from address 0 and reported "0 bytes remaining"
on every drive. The emulator now synthesizes a fixed 8 MB / 4 KB-block DPB
and an allocation vector whose used bits reflect the drive's actual file
usage, so free space is reported correctly.- More BDOS calls implemented (were silent no-ops returning 0). Function
40 (write random with zero fill) now aliases function 34 instead of
returning fake success while dropping the write — a real data-loss fix for
any program that writes via 40. Function 24 (return login vector) reports
all eight drives A:–H: active. Function 5 (list /LST:output) routes to
the console so a program's printer output stays visible. Function 32
(get/set user number) is now tracked and shared with theUSERcommand, so
a program's save/restore-user sequence is self-consistent (files remain a
single flat area, not segregated by user — a documented simplification).
Functions 7/8 (get/set I/O byte) now read and write the IOBYTE at its
page-zero home (0x0003) so a set-then-get round-trip is consistent (device
redirection has no effect in the single-console model, but the value is no
longer silently dropped). - BIOS jump table for direct-console software. Programs that bypass BDOS
and do console I/O straight through the BIOS jump table (MBASIC, WordStar,
Turbo Pascal, Infocom games) now work: a real 17-entry CP/M 2.2 BIOS jump
table is laid in high memory, the warm-boot pointer at 0x0001 points at its
WBOOT entry, and each vector traps to the host, which services the console
group (CONST/CONIN/CONOUT/LIST/PUNCH/READER/LISTST) against the live
session. Also fixed:STAT B:no longer strands you at theB>prompt —
a transient's internal drive select is now undone when it exits (the CCP
re-selects its own default each command cycle, as real CP/M does), so only
a bared:command changes drives; andSTAT's allocation vector no
longer overran the new BIOS jump table. - All sixteen CP/M drives A:–P: now available (was A:–H:). CP/M 2.2's
architectural maximum is 16 drives; the emulator now auto-creates a folder
for each underCPM/and reports all sixteen in the login vector. Each is
a formatted, empty drive the instant its folder exists — the CP/M directory
is synthesized from the folder's real files, so there is no format/CLRDIR
step. TYPEnow paginates in the emulator. It previously streamed the whole
file past the screen in one go; it now stops each screenful with the same
--More-- (SPACE, RET, Q)viewer the Gateway Shell uses (SPACE = next page,
RETURN = one line, Q/ESC = quit), expanding tabs and wrapping long lines to
the terminal width.
- Interactive programs no longer hang on console-status polling. BDOS 11
Documentation
- Documented that the Kermit client must be set to binary file mode before
transferring programs or data. Vintage Kermit clients default to text
(ASCII) mode, and a binary file sent that way silently loses bytes: a CP/M
client reads the file in 128-byte records and in text mode treats^Z(0x1A,
the CP/M end-of-file pad) as padding rather than data, so a^Zthat falls on
a record boundary is dropped and every following byte shifts down one
position. Nothing in the protocol catches it — every packet's block check
passes and the client reports success — so the file that lands is subtly wrong
rather than obviously truncated. Diagnosed from a real SC126 upload where an
8,704-byteWITNESS.COMand a 104,960-byteWITNESS.DATarrived 1 and 8
bytes short: nine dropped bytes out of 113,664, enough to stop the game
running but far too few to notice by comparing file sizes casually.
Re-uploading afterSET FILE MODE BINARYproduced files byte-identical to the
originals. The gateway itself always transfers binary, byte-for-byte, in both
directions — it never translates line endings and never trims padding — so
this is purely a client-side setting, which is why a download can be perfect
while an upload of the very same file is damaged. Documented in four places
with the per-client commands (kercpm3 / CP/M KermitSET FILE MODE BINARY,
C-Kermitset file type binary/-i, MS-DOS Kermit): a new aside in user
manual §8.8, a new user-manual troubleshooting entry §16.11 ("Transferred File
Won't Run (Wrong Size by a Few Bytes)") framed around the symptom as a user
meets it, and callouts on theweb/kermit.htmlandweb/kermitreference.html
reference pages. Each notes that the short-file refusal added earlier in this
release only fires when the client declares a length in its attribute packet —
many small clients send none, leaving the receiver nothing to check the
arriving size against — so binary mode is the habit to keep rather than
something to rely on the guard for. - Documented that packet counts are not an integrity check. The same pages
now warn against comparing the packet count of an upload against a download of
the same file: control bytes and high-bit bytes are quoted into two or three
wire characters each, so the expansion — and therefore the packet count —
legitimately differs with the negotiated packet length and with which side is
sending. A differing count is not evidence of data loss and a matching one is
not evidence of success; compare checksums instead. - Regenerated
usermanual.pdffrom the updated HTML perversionchange.txt
(WeasyPrint,Producerunchanged). The rebuild also picks up earlier HTML
edits that had never been rebuilt, so the PDF grows by more than the entries
above account for.