The fetch network adapter only intercepts port 80 (parsed as HTTP and
replayed via fetch). Everything else was RST'd. This adds a second
tcp-connection bus listener that bridges any other destination port
straight to a Node net.Socket in the renderer — no extra process, no
WebSocket relay, no new dependencies.
- net/tcp-relay.ts: hook v86 tcp-connection, accept SYN synchronously,
open a real socket to conn.psrc:conn.sport, buffer until connect,
pipe both directions, tear down on either side closing. Loopback,
link-local and multicast are refused; the rest of RFC1918 is left
reachable so the guest can still talk to LAN devices.
- net/dns-shim.ts: dns_method is now "doh" so the guest gets real IPs
for the relay to dial, but Cloudflare can't answer single-label names
like "windows95" or the NNN.external localhost magic. The shim wraps
global fetch, spots /dns-query POSTs for those names, and answers
with the same 192.168.87.1 placeholder the static resolver used to
hand out — the port-80 fetch path then takes over via the Host header
exactly as before.
- emulator.tsx: wire both in next to the SMB hook; set dns_method.
- debug-harness.ts: WIN95_PROBE_RUN/_RUN_AFTER/_RUN_WAIT to type an
arbitrary command into Start→Run (used to e2e-test the relay with
telnet against a host echo server).
* Add "Boot from scratch" to the Machine menu
Wires a new MACHINE_BOOT_FROM_SCRATCH IPC command from the app menu to the existing Emulator.bootFromScratch() handler, so users can start a fresh boot without going through the settings card. Enabled only while the machine is stopped, matching Start.
* Rename menu item to "Start without state"
* Move "Send key" items below power management in Machine menu
* Seamless mouse via VMware backdoor + fs-backed TOOLS share
v86 (rebuilt libv86.js from fork branch vmware-mouse): new VMwareMouse
device on port 0x5658 implementing GETVERSION/ABSPOINTER_* fed by the
existing mouse-absolute bus event. Move-only packets are coalesced so
the guest cursor never falls more than one frame behind. Emits
vmware-absolute-mouse on the bus when the guest driver toggles mode.
Renderer: listens for that event, keeps the v86 mouse enabled without
pointer lock, drops the startup auto-capture, and hides the host cursor
over the canvas (.seamless-mouse) while the driver is active. Falls back
to click-to-capture when no driver is present.
SMB: TOOLS share is now backed by the bundled guest-tools/ directory
(subdirectories work) with the synthetic README.TXT/_MAPZ.BAT overlaid
at the root. resolve() routes by tid; SEARCH and FIND_FIRST2 share a
single listForSearch helper.
guest-tools/mouse-driver/: VBMOUSE.EXE + VBMOUSE.DRV from VBADOS
(Javier S. Pedro, GPLv2). Load the TSR from AUTOEXEC.BAT and set
mouse.drv=vbmouse.drv in SYSTEM.INI to enable seamless mouse.
Also: tsconfig rootDir "." for TS 6.0 (preserves dist/src/ layout).
* docs: windows95-base now includes vmware-abspointer
SMB share:
- Mark dotfiles HIDDEN and host junk (.DS_Store, Thumbs.db, ...) HIDDEN+SYSTEM
so Explorer hides them by default but View > Show all files still works.
- Guard DOS device names (CON/PRN/AUX/NUL/CLOCK$/COM1-9/LPT1-9) in both the
long name and the generated 8.3 name; covers nul.tar.gz and 'con .txt' too.
- Replace trailing dot/space runs with '_' so names don't alias their stripped
form; block DEL and C1 control bytes.
- Fix duplicate listing of sanitized names (sfnMap was mutated mid-iteration).
- Thread attrs through OPEN/QUERY/FIND replies via DirEntry.attr / hostAttrs().
- New [5d] section in test-standalone.ts; 55 tests pass.
http file server:
- Remove the http://my-computer/ host-filesystem browser now that SMB covers
folder sharing. fileserver.ts now serves only static/www at http://windows95/
with a sep-suffixed traversal guard.
- Delete page-directory-listing.ts, page-error.ts, encoding.ts, hide-files.ts
and the three isFileServer* settings keys (and unused SettingsManager.delete).
- Point static/www/apps.htm at the SMB share instead.
Swap the unmaintained parcel-bundler@1.x for Vite's build API, called
from the same generateAssets hook. Output layout is unchanged
(dist/src/main/main.js + dist/static/index.html + dist/renderer.{js,css})
so no runtime path changes — __dirname-based asset lookup, loadFile, and
packagerConfig.ignore all keep working.
Renderer is built in lib/CJS mode with Node builtins + electron
externalized; a one-line banner aliases exports=module.exports since the
Electron <script> context provides module/require but not a bare exports
global. CSS (98.css + root.css) is now imported from app.tsx so Vite
emits a single renderer.css with fonts inlined.
Drops parcel-bundler and rimraf (vite-build clears dist/ itself).
~800ms full build.
Negotiate NT LM 0.12 (17-word response, Capabilities=0) so Win95 switches
from CMD_SEARCH (8.3-only) to TRANS2/FIND_FIRST2. Implement info level
0x104 (FILE_BOTH_DIRECTORY_INFO) with the win9x quirks Samba carries:
honor SearchCount/MaxDataCount (VREDIR drops the session if exceeded),
4-byte-align entries and the trans2 reply param/data blocks, dir
EndOfFile=0, FileName null-terminated with the null counted in
FileNameLength, and ShortNameLength=0 (a UCS-2 ShortName in an OEM
session GPFs shell32 on the single-directory probe Explorer does when
entering a subfolder). Add TRANS2_QUERY_FS_INFO (0x105/1/2), SMB_COM_SEEK
and core SMB_COM_READ, which the redirector falls back to with no
CAP_LARGE_READX. Sanitize >0xFF / Windows-reserved chars in display names
so emoji folder names don't truncate to '<' and wedge Explorer.
The user share is now named after path.basename of the mounted folder
(LANMAN-safe, ≤12 chars, with TOOLS/IPC$ collision avoided). A second
purely-synthetic TOOLS share holds _MAPZ.BAT and README.TXT so they
don't clutter the user's directory; treeConnect routes by share name to
a TID and every path-resolving handler branches on it so TOOLS never
touches the host fs.
48 protocol tests; verified end-to-end in the emulator (browse \\HOST,
open both shares, list Downloads with 85+ mixed-name entries, navigate
subfolders, open files in Notepad).
When running via yarn start on a branch other than master/main, append
the branch name to the window title (e.g. "windows95 (my-feature)").
No-op in packaged builds.
The stylesheets only used Less for variables and nesting, both of which
are now native CSS features supported by Electron 41's bundled Chromium.
- src/less/ -> src/css/, all .less files renamed to .css
- @win-* variables -> :root custom properties + var()
- // comments -> /* */
- Dropped unused @win-silver
- Removed less devDependency and the dead 'less' npm script
(pointed at a non-existent tools/lessc.js)
* Rewrite update-v86.js for the current build pipeline
Both v86 fixes we've been carrying are now real branches on the fork
(PR #1540 electron-renderer-fs-loader, PR #1541 ide-shared-registers)
combined as felixrieseberg/v86:windows95-base. update-v86.js no longer
needs to patch sources at build time — it just builds whatever's checked
out and copies the result.
Gone: the fallback-to-copy.sh path, the skew-day check, the structural
regex patches for load_file/exportSymbol/fetch-bind, the phantom-slave
guard (both are in the branch), the --js-only flag. If you don't have
cargo/clang/java/closure, the script fails loudly — no silent fallbacks.
Added: sanity checks against the installed libv86.js for the invariants
our SMB integration and parcel-build shim depend on, so if upstream
changes something load-bearing we see it as a WARN at update time
instead of a runtime failure.
Tested end-to-end: 5/5 sanity checks, fresh boot SUCCESS in 32s.
* docs and skills: capture SMB/v86/testing knowledge from the session
- docs/smb-share.md: user-facing SMB integration overview (how to mount in
Win95, what's implemented, what's not). Points at the protocol-level
README inside src/renderer/smb/ for wire-level gotchas.
- .claude/skills/probe-win95: how to boot and test the VM without a human.
Env vars, file locations, failure modes, the XT scancode keyboard trick,
bisect rules of thumb.
- .claude/skills/update-v86: how to pull upstream v86 changes, what the
five sanity-check WARNs mean, how to retire the fork branches when the
PRs merge upstream.
.gitignore narrowed to exclude only the runtime dirs (scheduled_tasks.lock,
worktrees) instead of the whole .claude/ tree, so skills can be committed.
The tab, file picker, and prop wiring are all back and styled to match
the new Properties sheet, gated on CDROM_ENABLED=false until v86's IDE
CD path works again.
Replace the sparse start/settings cards with a Win95-styled launcher:
- Start screen is now a 'Welcome to Windows 95' dialog with a gradient
side stripe, a rotating tip box, and a button column.
- Settings is a tabbed Properties sheet (Floppy / Network / State) with
group boxes, sunken read-only path fields, and OK/Cancel.
- Vendored 98.css and its Pixelated MS Sans Serif fonts; dropped the
old 95css-based .btn/.card/.nav classes.
- Removed the bottom taskbar nav (start-menu.tsx) — navigation now goes
through the dialog buttons so the launcher isn't mistaken for the
running OS.
Patches src/ide.js before building: restores the dual master+slave writes
for ATA Command Block registers (Features, Sector Count, LBA Low/Mid/High)
that 1b90d2e7 changed to current_interface-only. Those registers are
channel-shared per ATA spec; Win95's ESDI_506.PDR writes them then switches
drive-select expecting the values to persist. Found via JS-only bisect.
Boots fresh in ~32s with the same sporadic-bluescreen rate as the prod build.
The new build has the tcp-connection bus event so SMB uses the clean path
instead of the connection-theft hack (guard added for the old-API monkeypatch
to skip if the bus handler already accepted).
tools/update-v86.js applies the source patch automatically. v86 checkout
left clean (patch stashed).
- Show CPU as M/s (millions of instructions/sec) instead of raw count
- Replace Disk Idle/Read with actual R/W throughput (B/K/M per sec)
- Add Net ↓/↑ throughput from eth-receive-end / eth-transmit-end
- Always mount the bar; when hidden it slides off-screen and reveals on
hover near the top edge (Pin/Hide toggle)
- Center via translateX(-50%) so the wider bar stays centered
Windows 95 can now mount a host folder as a network drive at \\HOST\HOST.
Read-only, ~1500 lines, zero deps. Defaults to ~/Downloads, configurable in
Settings.
Protocol: NEGOTIATE (LANMAN2.1), SESSION_SETUP, TREE_CONNECT, TRANSACTION/RAP
(NetShareEnum, NetServerGetInfo, NetWkstaGetInfo), TRANSACTION2/FIND_FIRST2,
SEARCH (8.3 with ~N suffix mapping), OPEN_ANDX, NT_CREATE_ANDX, READ_ANDX,
CLOSE, QUERY_INFORMATION, CHECK_DIRECTORY. NetBIOS Name Service on UDP 137
answers Node Status and Name Query so \\HOST resolves.
v86 hook: monkeypatches adapter.on_tcp_connection (old API), shadows
adapter.receive during a port-80 probe to steal a TCPConnection without
side effects, re-aims it at port 139. Data via .on_data (Closure
dead-code-eliminated .on/.emit). Also registers tcp-connection bus event
for newer v86 builds.
Security: read-only, path traversal blocked lexically and through symlinks
(realpath the deepest existing ancestor, re-append tail, confirm under root).
Share path validated in main-process IPC.
BIOS updated to SeaBIOS 1.16.2 (compatible with old v86). v86 itself stays
on the Feb 2025 prod build — newer builds hang at the splash screen on fresh
boot (bisect tooling included in tools/).
Also: tools/update-v86.js builds wasm+libv86+BIOS from a local v86 checkout
and refuses to install JS/wasm pairs more than 14 days apart (copy.sh ships
mismatched pairs). tools/parcel-build.js dynamic-import patch made tolerant
of post-d4c5fa86 builds.
The previous build patched libv86.js by exact-string match against
Closure-mangled identifiers (k.load_file, H.exportSymbol, pa, qa),
which broke on every upstream rebuild.
Of the three old patches:
- exportSymbol order: now a one-line HTML shim copying module.exports.V86
to window after libv86 loads
- this.fetch binding: fixed upstream
- load_file XHR vs fs: replaced by patching await import('node:fs/promises')
to require('fs').promises - string literals survive Closure, fails loud
if absent
Also adds tools/update-v86.js to pull new builds from copy.sh, and exposes
the renderer DevTools protocol on localhost:9222 in dev.
React 19, Electron 41, TypeScript 6, electron-forge 7.8, plus everything
else to latest. Migrated to React 19 createRoot API, updated for Electron 41
session/window type changes, and adjusted tsconfig for TS 6 deprecations.
Regenerated @electron/packager patch for 18.4.4.