Auto-map \\HOST to Z: from the W95TOOLS guest agent (#364)

* Auto-map \\HOST to Z: from W95TOOLS at login

W95TOOLS.EXE now calls WNetAddConnectionA("\\\\HOST\\HOST", NULL, "Z:")
on a short retry timer (5 tries, 3s apart) so the shared folder shows up
as a drive without a trip through Start -> Run. MPR.DLL is LoadLibrary'd
so the EXE keeps its USER32/KERNEL32-only import table and still launches
if MPR is somehow absent. Skipped if Z: is already taken; gives up
silently if no share is configured.

Works for any user folder because the SMB server's tree-connect already
routes every share name other than TOOLS/IPC$ to the user share; added a
comment in server.ts pointing at the dependency.

Verified by cold-booting the image with the new vs. old binary in
StartUp: new -> tree connect to \\HOST\\HOST within ~5s of desktop and
z:\ opens in Explorer; old -> no SMB traffic after 55s at desktop.

* Drop rebuilt W95TOOLS.EXE from the diff

Binary will be rebuilt and baked into the image alongside the next
default-state re-bake; keep this PR source-only.

* Stop tracking guest-tools/agent/W95TOOLS.EXE

It's a build output of `make -C guest-tools/agent` and CI doesn't
consume it (the disk image is baked out-of-band), so there's no reason
to carry the binary in git.
This commit is contained in:
Felix Rieseberg
2026-04-12 09:16:54 -07:00
committed by GitHub
parent b74e6c7b0a
commit d38355ff16
5 changed files with 54 additions and 9 deletions

1
.gitignore vendored
View File

@@ -15,5 +15,6 @@ trusted-signing-metadata.json
.env
electron-windows-sign.log
.npmrc
guest-tools/agent/W95TOOLS.EXE
/.claude/scheduled_tasks.lock
/.claude/worktrees/

View File

@@ -25,11 +25,13 @@ Install inside the guest:
## agent/ — W95TOOLS guest agent
`W95TOOLS.EXE` is a hidden-window agent that talks to the emulator over
the VMware backdoor (port 0x5658). Currently it does one thing: bridges
Windows 95's `CF_TEXT` clipboard to the host (legacy backdoor commands
69; host side is `src/renderer/clipboard.ts`, which polls Electron's
clipboard). It's also where time sync, host-initiated shutdown, and a
tray icon will live when those land.
the VMware backdoor (port 0x5658). Currently it bridges Windows 95's
`CF_TEXT` clipboard to the host (legacy backdoor commands 69; host side
is `src/renderer/clipboard.ts`, which polls Electron's clipboard) and
auto-maps `\\HOST\HOST` to `Z:` at login via `WNetAddConnection`, so the
shared folder shows up as a drive without a trip through Start → Run.
It's also where time sync, host-initiated shutdown, and a tray icon will
live when those land.
Install inside the guest:

Binary file not shown.

View File

@@ -1,7 +1,8 @@
/*
* W95TOOLS — guest-side integration agent for the windows95 emulator.
*
* Currently: bidirectional text clipboard. Talks to the emulator over the
* Currently: bidirectional text clipboard, and auto-mapping the host's
* SMB share to Z: at login. Talks to the emulator over the
* legacy VMware backdoor (port 0x5658; implemented in v86's vmware.js).
* Joins the Win32 clipboard-viewer chain so guest copies are pushed
* immediately, and polls the backdoor on a timer so host copies show up
@@ -29,6 +30,17 @@
#define POLL_MS 250
#define MAX_CLIP 0xFFFF
#define TIMER_CLIP 1
#define TIMER_MAP 2
#define MAP_TRIES 5
#define MAP_DELAY 3000
/* The host SMB server routes any share name other than TOOLS/IPC$ to the
* user's folder, so a fixed UNC works regardless of which directory they
* picked. */
#define MAP_DRIVE "Z:"
#define MAP_UNC "\\\\HOST\\HOST"
extern unsigned long bd(unsigned long cmd, unsigned long arg);
#pragma aux bd = \
"mov eax, 564D5868h" \
@@ -49,6 +61,27 @@ extern unsigned long bd_ebx(unsigned long cmd, unsigned long arg);
static HWND g_next;
static int g_ignore;
static int g_map_tries;
/* Map \\HOST to Z:. Loaded lazily so we don't grow a link-time dep on MPR;
* if the call fails (no share configured, network not up yet) the caller
* retries a few times on a timer and then gives up silently. */
static int map_host_drive(void)
{
typedef DWORD (APIENTRY *WNetAddConn)(LPCSTR, LPCSTR, LPCSTR);
static WNetAddConn fn;
DWORD rc;
if (!fn) {
HMODULE mpr = LoadLibrary("MPR.DLL");
if (!mpr) return 1;
fn = (WNetAddConn)GetProcAddress(mpr, "WNetAddConnectionA");
if (!fn) return 1;
}
if (GetDriveType(MAP_DRIVE "\\") > 1) return 1; /* letter taken */
rc = fn(MAP_UNC, 0, MAP_DRIVE);
return rc == NO_ERROR;
}
static void push_to_host(HWND hwnd)
{
@@ -114,7 +147,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
switch (msg) {
case WM_CREATE:
g_next = SetClipboardViewer(hwnd);
SetTimer(hwnd, 1, POLL_MS, 0);
SetTimer(hwnd, TIMER_CLIP, POLL_MS, 0);
SetTimer(hwnd, TIMER_MAP, 1, 0);
return 0;
case WM_DRAWCLIPBOARD:
@@ -129,12 +163,19 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
return 0;
case WM_TIMER:
if (wp == TIMER_MAP) {
if (map_host_drive() || ++g_map_tries >= MAP_TRIES)
KillTimer(hwnd, TIMER_MAP);
else
SetTimer(hwnd, TIMER_MAP, MAP_DELAY, 0);
return 0;
}
pull_from_host(hwnd);
return 0;
case WM_DESTROY:
ChangeClipboardChain(hwnd, g_next);
KillTimer(hwnd, 1);
KillTimer(hwnd, TIMER_CLIP);
PostQuitMessage(0);
return 0;
}

View File

@@ -373,7 +373,8 @@ export class SmbSession {
// Path is \\SERVER\SHARE — route by the share segment. Unknown names fall
// through to the user share so a stale `net use` (e.g. from before the
// user re-pointed the mounted folder) still connects to *something*.
// user re-pointed the mounted folder) still connects to *something*, and
// so W95TOOLS.EXE can hard-code \\HOST\HOST when it auto-maps Z:.
const share = reqPath.split(/[\\\/]/).pop()?.toUpperCase() ?? "";
const isIpc = share === "IPC$";
this.tid = isIpc ? TID_IPC : share === TOOLS_SHARE ? TID_TOOLS : TID_SHARE;