diff --git a/.gitignore b/.gitignore index 5d77128..4a5d826 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/guest-tools/README.md b/guest-tools/README.md index ed69af1..2c56486 100644 --- a/guest-tools/README.md +++ b/guest-tools/README.md @@ -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 -6–9; 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 6–9; 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: diff --git a/guest-tools/agent/W95TOOLS.EXE b/guest-tools/agent/W95TOOLS.EXE deleted file mode 100644 index d8903de..0000000 Binary files a/guest-tools/agent/W95TOOLS.EXE and /dev/null differ diff --git a/guest-tools/agent/w95tools.c b/guest-tools/agent/w95tools.c index 29edaad..7195671 100644 --- a/guest-tools/agent/w95tools.c +++ b/guest-tools/agent/w95tools.c @@ -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; } diff --git a/src/renderer/smb/server.ts b/src/renderer/smb/server.ts index 281d44c..c70e629 100644 --- a/src/renderer/smb/server.ts +++ b/src/renderer/smb/server.ts @@ -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;