Repository navigation
Releases
V9x Remote Agent v0.17.0
Everything since v0.15.0, newest first.
0.17.0 (2026-11-12)
Remote-help operator-friction improvements (2026-11-12).
Added
wait-desktop -DismissLogon: wait inside the guest, optionally cancel an
English Windows 9x startup logon dialog, and reportLogonDismissed.
No credentials, provider changes or password-list changes. Unknown dialogs
and NT logons are left alone. Cancellation is recorded in the activity log.reboot/reset -DismissLogon: after reconnect proof, cancel the logon
and wait for Explorer on the same connection.-WaitSecondsbounds each
phase separately. Unsupported agents are refused before power control.
MCP:dismiss_logonon the desktop wait and reboot/reset tools.- Protocol:
DESKTOP_WAIT_REQUEST/ response0x0065/0x8065,
capabilityV9X_CAP_LOGON_DISMISS0x02000000(Win9x only). One request
waits at most 60 s, likePROCESS_WAIT, so a caller that drops mid-wait
cannot hold a connection or relay session; CLI and MCP repeat it up to
-WaitSeconds. - Positional guest paths for
list,cat,stat,getandhash, including
their aliases. Named paths remain supported; duplicate paths are rejected. reg -Set -Key <key> -Name <value> -String <text> | -Dword <n>and
reg -DeleteValue: one value per operation, with the old value returned
and recorded in the activity log. Hive roots, HKLM system/security/hardware
branches, HKCC/HKDD and the agent's startup entries are protected. No key
tree deletion. New/current values are limited to 1024 bytes. MCP:
v9x_reg_write(set_string,set_dword,delete_value).display [-Restore]: current versus registry mode; restore calls
ChangeDisplaySettings(NULL, 0).kill -RestoreDisplaywaits for the
process to exit before restoring. MCP:v9x_display,restore_display
onv9x_kill. If the exit wait or restore fails, the completed kill is
still reported (CLI: kill result plusDisplay.Error, exit 51).input -Window <title>: select one window by literal, case-insensitive
title substring, foreground it and verify focus before sending input.
When the foreground lock refuses the switch, the agent retries once with
its thread attached to the current foreground input queue.
Missing/ambiguous/unfocusable targets are refused; losing focus stops
remaining actions and releases keys/buttons pressed by that batch.
New input replies includeForegroundBefore,KeyboardWindow(last
key press/typed character) andForegroundAfter, with HWND, PID and title. MCP
input tools usewindow, also used for an optional screenshot crop.- Guest-side
PROCESS_WAITuses a real process handle in bounded chunks
of at most 60 seconds, reducing repeated relay process-list requests.
CLI/MCP use it for a PID or one exact-name match; wildcard/multiple-name
waits and older agents retain the existing process-list path.
Changed
set-autologon.ps1forwards-Relay/-RelayServerto every operation.
Its-Dismissnow uses cancellation throughwait-desktop -DismissLogon
(requires 0.17.0+), without registry export or a foreground Enter key.
-Enableretains its explicit blank-password logon behavior.- Relay reconnects now use the wait's remaining budget instead of opening
every session with a minimum 150-second allowance. The relay's open
deadline includes the initial HTTP request. Guest-side waits allow
transport overhead separately, including the measured slow relay path. - Discovery's host fixture waits for an actual ready responder instead of
a fixed startup sleep; network waits are bounded so failure cannot hang
cleanup.
Live-tested in 86Box. Windows 98 SE: reset -DismissLogon cancelled the
startup logon dialog and reached Explorer; targeted input behind
another window and into a minimized one; display and kill -RestoreDisplay.
Windows 2000 SP4: targeted input past the foreground lock (including a
minimized target after another window had keyboard input), display,
kill -RestoreDisplay, registry set/change/delete with protected-key refusal,
and the native process wait. The physical machine's network-password variant
and the relay path are not yet live-tested. The original 0.15.0 process-wait
hang has not been reproduced: the reconnect-budget defect is fixed and the
new process wait avoids the repeated relay polling path, without claiming
that the original incident's root cause is proven.
0.16.0 (2026-11-06)
Crash detection in one call: wait until a program exits or a fault dialog
about it appears, and get the dialog's text back, instead of polling
windows and processes or taking screenshots.
Added
wait-fault [-Pid <id> | -Name <image>] [-Dismiss]: exit 0 with
Outcome = Exitedwhen the process ends, exit 50 withOutcome = Fault
when a fault dialog about it appears.Faultholds the dialog'sTitle,
Message,DetailsandButtons. On Windows 9x,Detailsis the
register dump ("CRASHPRB caused an invalid page fault in module
CRASHPRB.EXE at 016f:004010e8. Registers: ..."), read without pressing
Details. On Windows 2000 it waits for Dr. Watson to finish its log
(closing the box earlier cancels the log) and gives the exception line
("c0000094 (divide by zero)"). The dialog is matched by the image name in
its title or text as well as by owner, because Windows 9x shows it from
the KERNEL32 system process and Windows 2000 from Dr. Watson or CSRSS.
Faults are checked before exit, because the dialog outlives the process.
Without-Pid/-Nameit watches every program until-WaitSeconds
(exit 0,Outcome = NoFault).-Dismisscloses the dialog. Alias
wait-crash. MCP:v9x_wait_fault.close-window -Title <text>: WM_CLOSE to the one matching top-level
window, then wait until it is gone. It refuses the desktop, the taskbar
and the agent. MCP:v9x_close_window.windows -Controls: each window's buttons, labels and edit text.
MCP:v9x_windowscontrols.- Protocol:
WINDOW_TEXT_REQUEST0x0063/0x8063and
WINDOW_CLOSE_REQUEST0x0064/0x8064, capability
V9X_CAP_WINDOW_TOOLS0x01000000. tests\integration\crashprb.c: a program that crashes (NULL write,
divide by zero), exits, shows a message box or hangs on request.
Changed
exec -WaitForExitreturns on a crash. A crashed program stays alive
while its fault dialog is up, so the wait used to sit out-WaitSeconds
(an hour by default). On a 0.16.0+ agent it uses thewait-faultprobe:
exit 50 withOutcomeandFault, and it takes-Dismiss(MCP
wait_for_exitsetsresult: "fault"and takesdismiss). Older agents
wait as before.
Tested on Windows 98 SE and Windows 2000 SP4 in 86Box: NULL write, divide
by zero, a delayed crash, clean exit, an ordinary message box (not a
fault), a quiet watch, and -Dismiss leaving no dialog behind.
0.15.1 (2026-11-06)
Fixed
update -Relayreplaced only the agent. Its version check and the
replacement ofV9XVNC.EXEandSETUP.EXEran as inner v9xctl calls
that dropped-Relayand went to the local default port, so on
remote-help machines VNC and the tray's Settings page stayed on the old
version ("WARNING: V9XVNC.EXE was not updated: Agent at
127.0.0.1:9869 is unreachable"). The inner calls now use the same
relay machine and server. Host-side only; the guest agent is unchanged.
V9x Remote Agent v0.15.0
Everything since v0.13.1, newest first.
0.15.0 (2026-11-06)
What a tester at the machine can see: a chat that survives a reboot, and a
plain-English activity log of what the developer and the agent are doing.
Asked for by a remote-help tester.
Added
- Activity log,
C:\V9XREMOTE\EVENTS.LOG, opened from the tray's new
Activity log... item (Notepad). One dated line per event a person at
the machine cares about: agent started (with version), developer connected
(LAN address or remote help), "Idle: no developer activity for 2 minutes
(safe to reboot or shut down)", reboot / shutdown / hard reset / agent
update requested by the developer, remote help connected, unreachable,
back in touch after a long silence, paused or resumed at the machine.
Rotates toEVENTS.OLDat 64 KiB.AGENT.LOGstays the technical log. note -Text "...": the developer adds a line to that log
("Developer: attempting driver test 12") before doing it, so the record
is written as things happen rather than after. NewEVENT_NOTE_REQUEST
0x00A0/EVENT_NOTE_RESPONSE0x80A0, capability
V9X_CAP_EVENT_LOG0x00800000, 1-200 ASCII characters. MCP:
v9x_note. Older agents: exit 23.- Chat history kept across reboots in
C:\V9XREMOTE\CHAT.LOG
(CHAT.OLDpast 64 KiB); the chat window reloads the newest 8 KiB when
the agent starts. Chat lines now carry the date. remote-poll-gapinAGENT.LOGwhen a remote-help check-in comes more
than 30 s after it was due, with how long the agent was silent: the
measurement the "silent guest during 3D" issue asked for.
Changed
- A busy remote-help machine is refused, not taken over. A second
-Relaycall (or MCPrelay) while another caller is still using the
machine's relay session (a longwait-window, say) used to end that
call's session. The update server (plugin DB version 4) now refuses it,
saying which verb holds the session and for how long, and v9xctl exits
21. A caller's own reconnects still replace its session (each v9xctl
process and each MCP tool call sends an owner id), a session nobody has
touched for 60 s is replaced as before, and-RelayTakeOver(MCP
relay_take_over) takes a live one on purpose. - Several relay calls at once. The agent runs up to three relay sessions
side by side, each on its own thread and loopback connection, and says
max_sessions=3/sessions=a,bin its check-in; the server (plugin DB
version 5) lets that many in, so ascreenshot,windowsorcatworks
while a longwait-windowholds the first session. Check-ins keep their
normal pace during a session (they used to drop to one every 15 s), and a
session whose server vanished ends after 5 minutes. Agents before 0.15.0
still get one session (and the busy refusal above); a 0.15.0 agent on an
older server gets the single session it offers. GET machinesreportsbusyasnullonce the machine is offline (its
last poll is too old to say), and itssessionrow names the verb, age
and whether it is in use.
0.14.0 (2026-10-29)
Waiting for long jobs (benchmarks, installers, test runs) without sleeping,
polling by hand or guessing how long they take.
Added
wait-process -Pid <id> | -Name <image>: returns when no such guest
process runs (exit 49 at the deadline;WasRunning = falsewhen nothing
matched on the first look). MCP:v9x_wait_process. Works with 0.13.0
agents (PROCESS_LIST).exec -WaitForExit: launch detached, then wait until that process
has exited: no 1-hour exec cap, no execution slot held (no output or exit
code). MCP:wait_for_exitonv9x_exec.- The child's pid in exec results (
Pid; MCPpid). New capability
V9X_CAP_EXEC_PID0x00400000and EXEC option0x0002make
EXEC_COMPLETE32 bytes with the pid; hosts ask only when it is
advertised. Against 0.13 agents-WaitForExitwaits on the image name. wait-file -Match <text>(andexec -WaitForFile ... -Match): until
the file has a line containing the text, for reports that exist from the
start and are marked finished later (Complete=yes). MCP:matchon
v9x_wait_fileand on exec/shell. Agent 0.13.0+.cat -Follow [-Until <text>]: print lines as the guest appends them,
one flushed stdout line each, for a line-streaming monitor (Claude Code's
Monitor tool). Waits for a missing file, restarts when the file shrinks,
never prints a half-written last line, and-Untilstops on the first
line containing the text, including one already in the file.- AGENTS.md: "Long jobs" (run the blocking wait as a background command
and be woken when it exits, instead of sleeping or holding the
conversation) and "Old agents: update first".
Changed
- Every v9xctl wait (wait-file, wait-window, wait-process, cat -Follow, the
exec waits) polls every 250 ms for the first 10 s, then once a second, so
a long wait barely loads a guest that is benchmarking; the MCP waits do
the same. - A connection that drops in the middle of a v9xctl wait is reopened for
the time the wait has left ([connection lost ...; reconnecting]on
stderr) instead of failing with exit 20. -WaitSecondsaccepts up to 86400 (was 600);wait-process,
exec -WaitForExitandcat -Followdefault to 3600.
V9x Remote Agent v0.13.1
0.13.1 (2026-10-24)
Added
- Unattended self-update for hosts.
SETUP.EXE /update /Sruns the
same check, signature, version and SHA-256 checks as/update, with no
windows or questions and never a reboot, so a host can trigger it over
EXEC (V9 Retro Controller does, from the update server directly). The
installed copy waits for its child, so the exit code is the result: 0
staged, 1 failed, 3 nothing newer or nothing published. The outcome is
also written toC:\V9XREMOTE\TEMP\UPDATE.INI([result]status=
staged/current/none/failed,installed=,latest=,message=). A staged
update is the usualV9XNEW.EXE/V9XSNEW.EXE, so the host can apply it
at once withUPDATE_REQUESTinstead of waiting for a restart.
Changed
SETUP.EXE /updatewithout/Sis unchanged, except that "nothing newer"
still exits 1 there (3 is the unattended mode's code).
V9x Remote Agent v0.13.0
Everything since v0.12.0, newest first.
0.13.0 (2026-10-23)
Added
- Smaller, faster screenshots.
screenshotandinput -OutFiletake
-Region x,y,w,h,-Scale <0.01-1>and-Gray; the MCP's
v9x_screenshotand every input tool takeregion,scaleandgray.
The result reportsRegionX/Y/Width/HeightandScalePercentso image
coordinates map back to the screen. - The agent shapes the capture before sending it (new capability
V9X_CAP_SCREENSHOT_OPTIONS0x00020000, an options block after the
SCREENSHOT_REQUEST path;src\common\shotxf.c): crop, box-filtered
downscale, and lossless packing. A screen with at most 256 colours (every
8-bpp desktop and most dialogs) goes out as 8-bit with an exact palette,
RLE8-compressed when smaller, instead of 24-bit. New hosts always ask for
that, so even a plain screenshot crosses the wire (and the remote-help
relay) in a fraction of the bytes.-Grayis the lossy option. - Against older agents, v9xctl and the MCP crop/scale/grey on the host after
the download, so the options work everywhere. - The MCP reads 8-bit and RLE8 BMPs and sends greyscale PNGs as one channel.
- The verbs agents kept improvising (each its own capability, so older
agents are refused cleanly with exit 23):cat -Path <file> [-Tail N] [-Match text] [-MaxBytes n]/ MCP
v9x_read_text: a text file inline, filtered and bounded in the guest,
instead ofgetand reading the download (V9X_CAP_FILE_TOOLS
0x00040000,FILE_READ_TEXT; filter insrc\common\textscan.c).hash -Path <file> [-Source <local>]/v9x_hash: size, CRC32 and
SHA-256 computed in the guest, compared with a local file on request.delete -Path <path> [-Recurse] [-Force]/v9x_delete; refuses roots,
the Windows and system directories,C:\V9XREMOTEand their parents.ps [-Name]/v9x_psandkill -Pid|-Name/v9x_kill
(V9X_CAP_PROCESS0x00080000, toolhelp): processes started byexec
are marked with their request id, so a job holding the slot (BUSY on
screenshot, reboot or update) can be found and ended; itsexec
completes as usual. The agent and Windows core processes are refused.
Exit 45.reg -Key HKLM\.../v9x_reg_query(V9X_CAP_REGISTRY0x00100000):
subkeys and decoded values of one key, read-only, instead of
REGEDIT /Eandget. Exit 46.
- Waits instead of sleeps. Transcripts showed thousands of
Start-Sleepcalls between v9xctl calls; each now has a verb or option
that returns as soon as the condition holds (AGENTS.md "Never sleep and
poll"):wait-file -Path [-Gone] [-StableMs]andexec/shell -WaitForFile
(stat polling on one connection; every agent). MCPv9x_wait_file,
wait_for_file. Exit 47.windows,wait-window -Title [-Gone],exec -WaitForWindowand
screenshot -Window <title>(captures that window only) on a new
WINDOW_LISTrequest (V9X_CAP_WINDOWS0x00200000). MCP
v9x_windows,wait_for_window,window. Exit 48.- Connections dropped before HELLO are retried twice by default;
-WaitForAgent N(any verb) retries every transport failure for N s
with backoff.wait-desktopandping -WaitSecondswait for the agent
too. MCP: same retry,v9x_pingwait_seconds.
- v9xctl usage:
help <verb>; a one-line unknown-verb error (verb list, this
copy's version and path) instead of the full help; verb aliases agents
guess (ls,pull,status,rm,type,tasklist,wait-ready...);
-Target host:port;-HostName/-EndpointPortaliases. - The tray icon carries a small cloud in its bottom-right corner while remote
help is connected to the relay: white with a blue outline, or grey while
remote help is paused. It sits on every state, so the white/yellow resting
body and the lime command flash work as before.
Fixed
get(andstat) could return a stale or garbage copy of an INI file that
a running program had just written withWritePrivateProfileString:
Windows 9x caches that file in memory and writes it lazily, which is why
shell TYPE(a DOS VM start flushes it) showed the right contents.
stat,get,hashandcatnow flush that file's cache first.
Reproduced on 98 SE: a five-key INI read back as 17 bytes of garbage before,
intact after.help update(anyhelp <verb>) failed parameter binding: the second
positional argument only accepted vnc's status/start/stop.
0.12.1 (2026-10-20)
Added
- One GUI step in one call:
v9xctl.ps1 input -OutFile <image>and
screenshot: trueon every MCP input tool send the input, wait
-SettleMs/settle_ms(default 500) and capture a screenshot on the same
connection. Host-side only; no protocol or guest change.
Changed
v9x_inputwith rawmoverelactions now checks that the agent supports
relative mouse moves, asv9x_movealready did.
V9x Remote Agent v0.12.0
Everything since v0.9.1, newest first.
0.12.0 (2026-10-20)
Added
- Remote help (docs\remote-help.md): the owner of a real Windows 9x or
2000 machine can lend it to the developer over the Internet, and the
developer drives it with every existing verb and MCP tool.- Off by default. The tray's new "Remote help..." item opens
SETUP.EXE /remote, which explains what lending allows, takes a name,
GitHub user and email, and sets[remote] mode=on. The agent then
registers its install ID with the update server, which issues a
machine id (M-XXXXXX), and checks in every 60 s (2 s while a session
or chat is active). "Pause remote help" on the tray blocks commands
until the agent restarts; chat keeps working. - Signed relay. A session is a byte tunnel from the developer's PC to
the agent's own handlers, through the server over plain HTTP. Every
chunk towards the agent is Ed25519-signed on the developer's PC by the
newbuild\tools\v9xsign.exeand checked by the agent against
include\v9xremote\remote_key.h, bound to an agent-chosen nonce and a
sequence number, so neither the server nor anyone on the path can run
anything; a bad chunk ends the session (relay-bad-chunk). - Host side.
v9x_mcp.pytakesrelay="M-XXXXXX"on every agent tool
(or--relay), and addsv9x_relay_machinesandv9x_chat;
v9xctl.ps1takes-Relay M-XXXXXXon every verb. Uploads keep 1 MiB
of chunks in flight through the relay. Reboot and update proofs
reconnect through a new session. - Chat. Two-way: a message from the developer pops up in the agent's
chat window (tray: "Chat with developer..."), and the owner's replies
reach the developer's dashboard and MCP. - In use. The agent remembers when the last command (anything but
HELLO and PING) arrived and from where (a LAN address orrelay). The
tray tooltip reads "In use: last command N s ago from ..." for a minute
afterwards; the age also goes to the server and toC:\V9XREMOTE\STATUS.INI,
which the/remotepage shows. - Server. The
v9x_update_checkerplugin (separate repository) gains
the agent and developer endpoints, a Machines tab (online machines,
owners, state, last command, chat) and MCP toolsremote_list_machines,
remote_get_machine,remote_chat_send,remote_chat_readand
remote_set_machine, none of which can command a machine.
- Off by default. The tray's new "Remote help..." item opens
- Self-update. Tray "Check for updates..." runs
SETUP.EXE /update: it
asks the update server, verifies the release's Ed25519-signed SIGNED.TXT
againstinclude\v9xremote\release_key.hand the downloaded zip's size
and SHA-256 against that, shows the release notes, and on the owner's
yes stages the new version through the normal upgrade path (it takes over
at the next restart).[update] check_on_startup=1(a tickbox on the
/remotepage) checks quietly after start and turns the tray item into
"Install update x.y.z...". The crypto, inflate and zip reader are ported
from the Velocity9x updater, with its host tests. make-release.ps1also buildsv9x-remote-agent-<version>-win32.zip, the
self-update package, and the release workflow publishes it;
scripts\sign-release.ps1signs it afterwards on the developer's PC.
Changed
- The DOWNLOAD verb's HTTP client moved to
src\guest\http.c, shared with
remote help and SETUP.EXE; a silent server now times out after 60 s
instead of hanging the request. - SETUP.EXE imports WSOCK32 (for
/update). publish-github.ps1strips the update server's exact host name before its
private-name check.
0.11.0 (released as part of 0.12.0)
Added
V9XVNC.EXE: live remote control over standard VNC (RFB 3.3/3.7/3.8), so
any VNC viewer can watch the screen and drive the keyboard and mouse.- Off by default. It serves only when
AGENT.INIhas[vnc] enabled=1(exit 9 otherwise). SETUP.EXE has an "Enable VNC remote
control" option, unticked by default;/vncand/novncset it for a
silent install. Ticked, setup writesenabled=1, starts the server from
HKLM\...\Runat every logon (on Windows 9x and 2000, since it needs a
desktop) and starts it now. Unticked, or on uninstall, setup removes that
and stops a running server.INSTALL.BATcopies the binary but never
enables it. - Separate process. It runs apart from the agent, like
V9XSHOT.EXE,
because GDI capture can hang in a broken display driver; the agent never
imports GDI32. Inside the server every GDI call runs on one worker
thread. A capture that takes over 5 s is logged ascapture-stalled:
the server keeps reading input and sends no frames until it recovers
(capture-resumed). If setup or teardown is stuck for 15 s, it logs
gdi-wedged-exitand exits 10, releasing every held key first. - Settings. One viewer at a time, security type None unless a password
is set (see below),bind_address
andallowed_clienthonoured. Port and poll rate are in[vnc](default
5900, 100 ms). - Frames. The screen is polled into a 32 bpp DIB section and only
changed 16x16 tiles are sent, as Raw or Hextile, in whatever 8/16/32 bpp
true-colour format the viewer asks for; a colour-map viewer gets a fixed
3-3-2 palette. The cursor is drawn into the frame (Windows 98 and later).
A mode change is sent as DesktopSize, or drops a viewer that does not
support it. - Keys. X11 keysyms map to virtual keys, with extended keys flagged and
Shift added when a layout needs it. Everything held is released when the
viewer disconnects. - Cursor. A viewer that takes the RichCursor pseudo-encoding gets the
cursor shape on its own and draws it locally, so the frame no longer
contains it and moving the mouse dirties no tiles. The shape is rendered
over black and over white to find transparent and inverting pixels.
PointerPos reports moves the guest makes itself. Other viewers still get
the cursor drawn into the frame. - A second viewer gets RFB's connection-failed reply ("Another VNC
viewer is connected to this machine.") instead of hanging in the listen
backlog. - ZRLE. The deflate encoder is written for this project
(src\guest\vncz.c: fixed Huffman, greedy LZ77, a sync flush after each
rectangle, one zlib stream per connection), since the build has no C
runtime or zlib. Each tile takes the smallest of raw, solid, packed
palette, plain RLE and palette RLE. One full 1024x768 frame of the test
desktop at 32 bpp took 20 KB, against 104 KB for Hextile and 3.1 MB for
Raw. The server takes the first of ZRLE, Hextile and Raw in the viewer's
order. - CopyRect. When the foreground window turns up elsewhere at the same
size, the viewer copies the pixels it already has, and the tile diff
then sends only what the copy missed. - Clipboard both ways. Text only, as
CF_TEXTwith Latin-1 and CRLF on
the Windows side. Changes are found by polling every 0.5 s and compared
by hash, so text from the viewer is not echoed back. All clipboard calls
run on the GDI worker, so the watchdog covers an owner that hangs; its
wait now also handles window messages, because the clipboard owner window
must answer them. - Optional VNC password (
[vnc] password, at most 8 characters, a
masked field in SETUP.EXE). Viewers then use VNC authentication; DES is
insrc\guest\vncdes.cand checked against FIPS known answers by the new
host test. A wrong password is refused after 2 s.vnc statusand
v9x_vncreportPasswordSet, never the password. - Sampled capture. Reading the whole screen back was the bottleneck:
about 780 ms per 1024x768 frame on an emulated S3 ViRGE at 16 bpp,
against about 80 ms to diff and next to nothing to encode, which held a
viewer to roughly one frame a second. Each poll now reads one pixel row
per 16-row band of tiles, rotating which row through all 16. It then
reads in full only the tiles whose sampled row changed, those that
changed last frame, and the area under a drawn cursor, and the diff
checks only those tiles. A full read still happens for a full-frame
request and after a resize. In the same typing test, polls went from
10-12 to 98 per 10 s (about 54 ms each), so the 100 ms poll interval is
now the limit.perf-*lines inVNC.LOG(every 60 s and at session
end) give capture, diff, encode and send times. - Windows 2000. On NT the server installs and runs as a Windows service
(V9xRemoteVnc) as LocalSystem, instead of the Run key, because only
SYSTEM can open the Winlogon (secure) desktop. The capture and input
threads follow the input desktop withOpenInputDesktop/
SetThreadDesktop, so a viewer sees and can drive the lock / "Unlock
Computer" dialog and the logon screen, not just the user desktop. The
capture thread owns no window (the clipboard moved to its own thread,
which owns the hidden clipboard window) soSetThreadDesktopcan move
it.V9XVNC.EXE -install/-uninstallmanage the service (SETUP.EXE
does this on NT);-serviceis the SCM entry point. Sending Ctrl+Alt+Del
from a viewer is not supported (no pre-Vista API); pair the VM with
autologon. All of this is resolved at run time and is inert on Windows 9x
and Windows 95. Service support:src\guest\vncsvc.c. - Command line.
-stopand-status(exit 0 running, 1 not). - Test hook. While
C:\V9XREMOTE\TEMP\VNCSTALL.DATexists the capture
worker behaves as if wedged, sotests\integration\vnc_stall.pycan
drive the watchdog on a healthy machine.
- Off by default. It serves only when
- `v9xctl vnc status|...
V9x Remote Agent v0.9.1
Everything since v0.6.3, newest first.
0.9.1 (2026-10-09)
Changed
v9xctl updatealso upgrades agents too old to hot-update (before 0.6.0).
It used to judge that by capability0x0200, but 0.5.x advertises that bit
too, so against 0.5.x it uploaded both binaries and then failed on
UNSUPPORTED_OPERATION(exit 41). It now reads INFOAgentVersionfirst
and, below 0.6.0, drives the staged route those agents support, with every
guest path fixed and 8.3:
copy the package toC:\V9XPKG, run itsINSTALL.BATthere, check that
V9XNEW.EXEis staged, reboot soUPDATE.BATswaps it in from
RunServices, confirm the agent now reports the package's version and
nothing is left staged, then removeC:\V9XPKG. A session that improvised
this by hand put the package in a long-named folder, and the boot-time step
stopped at a prompt until someone pressed a key. The result carries
Mode = Staged; failures exit 41 (staging) or 42 (did not apply, see
C:\V9XREMOTE\UPDATE.LOG). It creates DOS VMs (INSTALL.BAT,
UPDATE.BAT), which agents before 0.6.0 cannot avoid. Verified by staging
0.5.2 to 0.9.1 on a Windows 98 SE guest. Host-side only; the agent is
unchanged from 0.9.0 apart from its version.- The MCP
v9x_updatetool checks the agent version the same way and refuses
an agent before 0.6.0 before uploading anything, pointing at
v9xctl update.
0.9.0 (2026-10-09)
Changed
- Idle connections are closed: a client that sends nothing for
AGENT.INI [agent] idle_timeoutseconds (default 300,0never) is
dropped and logged asclient-idle-timeout, except while it owns a running
EXEC. Before, a client that vanished without a FIN or RST reaching the guest
held one of the four connection slots, and kept the tray icon busy, until
the agent restarted. Applies to Win9x and NT; usesselect()only, so the
import table is unchanged.
Added
- Windows 2000 support for the Win32 agent and
SETUP.EXE. Windows 98
behaviour is unchanged: every difference is chosen at run time from
GetVersion()(src/guest/platform.c), and the import table is the same.- Autostart: NT ignores
RunServices, so on NTSETUP.EXEand the
hot-update swapper registerHKLM\...\CurrentVersion\Runinstead. The
agent then starts at logon, so pair it with autologon. Over an existing
install,SETUP.EXEstages the update by pointingRunat
V9XNEW.EXE -hotswaprather thanUPDATE.BAT.SETUP.EXE /uninstall
clears both keys on NT. - Reboot and shutdown enable
SeShutdownPrivilegefirst, which NT requires
beforeExitWindowsEx(also for SETUP.EXE's "Restart Windows now"). If
it cannot be enabled the request fails withACCESS_DENIEDinstead of
being accepted and then doing nothing. The token calls are resolved with
GetProcAddress, so nothing new is imported. - Shutdown on NT drops
EWX_POWEROFFwhen the machine has no soft-off
(GetPwrCapabilitiesreports noSystemS5, as with the "Standard PC"
HAL and APM off), because that HAL turns a power-off request into a
reboot. Such a machine halts at "It is now safe to turn off your
computer" and the agent logsshutdown-accepted-halt. Win9x still
always powers off. - Shell-mode EXEC runs
CMD.EXE /Con NT, whereCOMMAND.COMis the
16-bit interpreter under NTVDM. The wire format is unchanged. - Hot update: NT lets a running EXE be renamed, so on NT the swapper also
waits for the old agent's singleton mutex to disappear before swapping. - Sockets are not inheritable on NT. NT Winsock sockets are kernel handles
that children inherit by default, so the hot-update swapper (and any
EXEC child) held the listening socket and the relaunched agent looped on
listen-failedforever. Every agent socket now has its inherit flag
cleared (SetHandleInformation, resolved at run time), and on NT the
swapper relaunches the agent with no inheritance, which also recovers an
agent built without the fix. The swap log saysrun-seton NT. INSTALL.BATandUNINSTALL.BATstop on NT and point atSETUP.EXE.- INVENTORY reads the NT locations:
osfromWindows NT\CurrentVersion
(version_numberlike5.0.2195, plusproduct_type),biosfrom
HARDWARE\DESCRIPTION\System(plusvideo_version), anddisplay
bppandrefreshfromEnumDisplaySettingsA. Devices are the present
instances underSYSTEM\CurrentControlSet\Enum, with presence and
problem codes fromCFGMGR32(meta devices=live); an instance with no
Classvalue is classified through itsClassGUID.CFGMGR32and
EnumDisplaySettingsAare resolved at run time. The 9x records are
unchanged, apart fromREG_MULTI_SZvalues now being read (joined with
/) where before they were skipped. allow_dos_vm=0on NT refuses only a directexecof a DOS or Win16
program (anything that is not a PE image), sinceCMD.EXEand console
EXEs start no VM there;EXEC_SHELLstays advertised.SETUP.EXElabels
the option "Allow DOS and 16-bit Windows programs (NTVDM)" on NT.
- Autostart: NT ignores
0.8.1 (2026-10-07)
Changed
- A guest
downloadmay fetch up to 2 GiB (V9X_MAX_DOWNLOAD_SIZE). It
streams straight to the guest disk, so it no longer shares the 256 MiB
put/getceiling; a 256 MiB+ game archive failed with Win32 223
(ERROR_FILE_TOO_LARGE) on 0.8.0. The wire format is unchanged.
Added
- Relative mouse moves: INPUT opcode 7 (
s32 dx, s32 dy), capability bit
V9X_CAP_INPUT_RELATIVE(0x8000),moverel DX,DYinv9xctl input
andrelative=trueon the MCPv9x_movetool. Games that read motion
deltas and recentre the cursor (UT99's own menu cursor) never see an
absolutemove. Sent asmouse_eventmotion in steps of at most 4 pixels,
below Windows' default acceleration threshold of 6.
0.8.0 (2026-10-01)
Added
SETUP.EXE, a graphical install wizard (Welcome, Options, Install,
Finish), launched byAUTORUN.INFfrom the install CD. The Options page
sets LAN discovery, DOS programs (allow_dos_vm), TCP port, listen address
and allowed client, and shows the install ID. Over an existing install it
keeps the settings and ID and stages the update for the next boot, exactly
likeINSTALL.BAT; run fromC:\V9XREMOTEit only changes settings. It
registers in Add/Remove Programs (SETUP.EXE /uninstall), and/Sruns
a silent install.INSTALL.BATstays as the no-GUI path.- Install ID: an RFC 4122 v4 UUID in
AGENT.INI [agent] install_id,
generated by SETUP.EXE or on the agent's first start, so a machine can be
recognised after its IP changes. Win9x and DOS agents both have it. It is
reported in INVENTORYmeta install_id, and in INFO when the host asks
for it: capabilityINSTALL_ID(0x4000) means INFO accepts a u32 flags
payload, and flag 1 appends the ID afterallowed_client. Older hosts
send no flags and see the old reply, so nothing breaks either way. - LAN discovery (Win9x agent): with
discovery=1the agent runs an
mDNS/DNS-SD responder (RFC 6762/6763, Winsock 1.1 multicast, no Bonjour)
advertising_v9xremote._tcp.localwith SRV, TXT (id,ver,build,
dos,os,name) and A records, re-announcing when its address
changes and sending a goodbye before a hot update or reboot. INVENTORY
meta discoverysaysoff,onorunavailable. Off by default in
AGENT.INI; SETUP.EXE ticks it. v9xctl discoverlists agents that answer, and a global-Id <install id>
finds the target by discovery instead of-Host/-Port, and finds it again
if it comes back from a reboot on a new address.-QueryAddresssends the
query unicast to one address. The MCP server gainsv9x_discoverand
--id, with the loopback guard applied to the address discovery returns.
0.7.1 (2026-09-26)
Fixed
INVENTORYlists only devices that are loaded now. It walks the
Configuration Manager's live tree (HKEY_DYN_DATA\Config Manager\Enum)
and follows each node'sHardWareKeyintoHKLM\Enum, instead of reading
every instance underServices\Class, which also keeps devices the machine
had in the past. On a Velocity9x netbook 0.7.0 reported three display
adapters, five disk drives and a dozen USB controllers, and named the
stale "Standard PCI Graphics Adapter" as the display adapter; thedisplay
record now names the first working live Display node (the GMA 950).
devicerecords gainid(the hardware key, e.g.PCI\VEN_8086&...) and
problem(the Configuration Manager problem code, when nonzero). A final
metarecord now always saysdevices=live, ordevices=registrywhen
there is no live tree and the old walk is used.
0.7.0 (2026-09-26)
Added
INVENTORYmessage (0x0070 -> 0x8070) and capability bitINVENTORY
(0x2000): a hardware and software inventory read with calls that are safe
on every 9x release. The reply is a list of typed records (os,cpu,
memory,drive,display,net,bios,device,meta), each a set
of string key/value fields, so fields can be added later without another
protocol change. It covers the Windows product/build/sub-version, DirectX
and IE versions, CPUID vendor/brand/family/model/stepping/features and a
measured clock speed, physical and page-file memory, per-drive type, label,
file system and size, the display adapter and resolution, host IP
addresses, BIOS strings and the PnP device list (DriverDesc of the Display,
Monitor, MEDIA, Net, disk controller, CD-ROM, modem, port, USB and input
classes). Only local fixed disks are probed, with critical-error dialogs
suppressed, so an empty floppy or CD drive or a dead network share never
stalls or prompts. CPUs without CPUID (386/486) reportvendor=unknown.
The reply fits the 16 KiB response buffer; if it would not, the last
record ismeta truncated=1.- `v9xctl inve...
V9x Remote Agent v0.6.3
0.6.3 (2026-09-24)
Added
- The notification-area icon now shows activity. Its body flashes lime while
any client is connected or any execution is running, and a short hold keeps
it flashing after the last frame so even a barepingblinks once. The
agent counts claimed connection slots and every frame it receives or sends;
the tray worker samples those counters every 100 ms and swaps the icon at
250 ms intervals. Nothing about the protocol changes. - The icon itself is now drawn by the agent as a 16-colour icon resource in
memory (CreateIconFromResource) instead of borrowingIDI_APPLICATION:
the agent may not import GDI32, so this is the only way to have two colour
variants of one picture. If icon creation fails the agent logs
tray-createicon-failedand falls back to the stock icon without flashing. scripts\v9xctl.ps1explains itself. Running it with no verb, an unknown
verb, orhelpprints the verb list, the connection options (-Host,
-Port,-ConnectTimeoutSeconds) and worked examples instead of an
interactive prompt forAction; a verb missing its required parameter
prints that verb's usage line and an example. Comment-based help backs
Get-Help .\scripts\v9xctl.ps1 -Examples, verbs tab-complete, and a
connection failure now names the target it tried and how to override it.
V9x Remote Agent v0.6.2
0.6.2 (2026-09-04)
Fixed
- The notification-area icon, which had never appeared on any guest in any
release that shipped it.v9x_tray_startpassed a NULLlpThreadIdto
CreateThread, which Windows 9x rejects withERROR_INVALID_PARAMETER
(Windows NT accepts it, so no modern host ever flagged the call). The window
and icon were created fine; the worker thread never existed, so
Shell_NotifyIconwas never reached. Measured astray-thread-failed gle=87
on 98 SE. The owner window now also belongs to the worker thread and that
thread pumps its own message queue, instead of the window being created on
the main thread that blocks inaccept()forever. - Tray failures are no longer silent.
v9x_tray_startreturned 0 for three
different reasons behind onetray-icon-failedline, and a failing
Shell_NotifyIconwas retried every second with its result discarded, so a
wholly broken feature could not be diagnosed from the log. Each failure now
names itself with itsGetLastError(tray-window-failed,
tray-loadicon-failed,tray-thread-failed,tray-notify-failed) and
success logstray-icon-added.
Added
scripts\set-autologon.ps1: report, enable or disable Windows 9x autologon
on a guest, and clear a logon dialog that is already up (-Dismiss). Purely
host-side, composed from existing verbs, no protocol change.scripts\capture-emulator-window.ps1: capture the emulator's own window from
the host.screenshotneeds Explorer, so it returns exit 43 for precisely
the states worth seeing - a logon dialog, a BIOS prompt, a shutdown hang.
This reads the window instead and needs nothing from the guest.- Documented what the agent can and cannot do before anyone logs on.
RunServicesdoes start it before the logon dialog and everything except
screenshotandwait-desktopworks there, so a guest that prompts at boot
stalls automation at its firstwait-desktopwhilepingkeeps succeeding.
AGENTS.mdnow says to callinfobefore concluding a guest is hung.
V9x Remote Agent v0.5.2
0.5.2 (2026-08-13)
Fixed
- Screenshot capture and all GDI screen probing now run in the disposable
V9XSHOT.EXEhelper. A display-driver GPF or blocked GDI call can no longer
directly crash the network agent; the agent terminates a helper that has not
completed within 7.5 seconds and returns a normal screenshot error. - Capture is rejected unless Explorer's desktop is ready, and the helper
refuses a mode whose dimensions change during a short stability sample. V9XAGNT.EXEno longer imports GDI32. HELLO and INFO use USER screen metrics
and report bits-per-pixel as unknown (0); successful screenshot responses
still report the source colour depth measured by the helper.- The Win98 install and staged-update paths deploy the helper with the matching
agent build. INSTALL.BATvalidates the complete package before modifying the guest,
checks every required copy, and removes both staged binaries if either copy
fails.UPDATE.BATinstalls the helper before activating the matching agent.
Changed
- Recovery guidance now forbids screenshots during fullscreen mode changes or
suspected display wedges. Use INFO, direct trace-dump execution, and file
download first, and capture only after desktop readiness is re-established. BootCounteris documented as an agent-start counter, not independent reboot
proof; reconnect, the persisted resume token, and desktop readiness are
required before post-reboot GUI work.