@@ -36,8 +36,78 @@ const SC = {
3636 R_DN : [ 0x13 ] , R_UP : [ 0x93 ] ,
3737 ENTER_DN : [ 0x1c ] , ENTER_UP : [ 0x9c ] ,
3838 BACKSLASH_DN : [ 0x2b ] , BACKSLASH_UP : [ 0xab ] ,
39+ ALT_DN : [ 0x38 ] , ALT_UP : [ 0xb8 ] ,
3940} ;
4041
42+ // WIN95_PROBE_VGATRACE=1 → wrap VGA I/O ports at the io.ports[] layer (the
43+ // VGAScreen.portXXX_write methods are captured by-value at registration time,
44+ // so monkey-patching them on the instance is a no-op for most ports). Each
45+ // entry is [port, op, value, "eip VMPE cplN"] so you can tell vgabios in V86
46+ // mode apart from the ring-0 display driver.
47+ const VGATRACE_FILE = "/tmp/win95-vgatrace.json" ;
48+ let vgaTrace : any [ ] | undefined ;
49+
50+ function armVgaTrace ( emulator : any ) {
51+ const cpu = emulator . v86 ?. cpu ;
52+ const io = cpu ?. io ;
53+ if ( ! io || vgaTrace ) return ;
54+ vgaTrace = [ ] ;
55+ const ctx = ( ) => {
56+ try {
57+ const ip = ( cpu . instruction_pointer [ 0 ] >>> 0 ) . toString ( 16 ) ;
58+ const vm = cpu . flags [ 0 ] & ( 1 << 17 ) ? "VM" : " " ;
59+ const pe = cpu . cr [ 0 ] & 1 ? "PE" : " " ;
60+ return `${ ip } ${ vm } ${ pe } cpl${ cpu . cpl [ 0 ] } ` ;
61+ } catch { return "?" ; }
62+ } ;
63+ const W = [ 0x3c0 , 0x3c2 , 0x3c4 , 0x3c5 , 0x3ce , 0x3cf , 0x3d4 , 0x3d5 , 0x3b4 , 0x3b5 , 0x1ce , 0x1cf ] ;
64+ const R = [ 0x1cf , 0x3da , 0x3c1 ] ;
65+ for ( const p of W ) for ( const w of [ "write8" , "write16" ] ) {
66+ const orig = io . ports [ p ] [ w ] ;
67+ io . ports [ p ] [ w ] = function ( v : number ) {
68+ vgaTrace ! . push ( [ p , w , v , ctx ( ) ] ) ;
69+ return orig . call ( this , v ) ;
70+ } ;
71+ }
72+ for ( const p of R ) for ( const r of [ "read8" , "read16" ] ) {
73+ const orig = io . ports [ p ] [ r ] ;
74+ io . ports [ p ] [ r ] = function ( ) {
75+ const v = orig . call ( this ) ;
76+ vgaTrace ! . push ( [ p , r , v , ctx ( ) ] ) ;
77+ return v ;
78+ } ;
79+ }
80+ console . log ( "[probe] vga trace armed" ) ;
81+ }
82+
83+ function dumpVgaTrace ( emulator : any ) {
84+ if ( ! vgaTrace ) return ;
85+ const d = emulator . v86 ?. cpu ?. devices ?. vga ;
86+ const state = d && {
87+ svga_enabled : d . svga_enabled ,
88+ graphical_mode : d . graphical_mode ,
89+ attribute_mode : d . attribute_mode ,
90+ miscellaneous_graphics_register : d . miscellaneous_graphics_register ,
91+ sequencer_memory_mode : d . sequencer_memory_mode ,
92+ clocking_mode : d . clocking_mode ,
93+ plane_write_bm : d . plane_write_bm ,
94+ crtc_mode : d . crtc_mode ,
95+ max_scan_line : d . max_scan_line ,
96+ underline_location_register : d . underline_location_register ,
97+ horizontal_display_enable_end : d . horizontal_display_enable_end ,
98+ horizontal_blank_start : d . horizontal_blank_start ,
99+ vertical_display_enable_end : d . vertical_display_enable_end ,
100+ vertical_blank_start : d . vertical_blank_start ,
101+ offset_register : d . offset_register ,
102+ dispi_enable_value : d . dispi_enable_value ,
103+ screen_width : d . screen_width ,
104+ screen_height : d . screen_height ,
105+ max_cols : d . max_cols ,
106+ max_rows : d . max_rows ,
107+ } ;
108+ fs . writeFileSync ( VGATRACE_FILE , JSON . stringify ( { state, trace : vgaTrace } ) ) ;
109+ }
110+
41111function sendChord ( emu : any , ...keys : { dn : number [ ] ; up : number [ ] } [ ] ) {
42112 for ( const k of keys ) emu . keyboard_send_scancodes ( k . dn ) ;
43113 setTimeout ( ( ) => {
@@ -80,10 +150,16 @@ export function startProbe(emulator: any) {
80150 // Enter, then optional WIN95_PROBE_RUN_AFTER keystrokes after _RUN_WAIT ms.
81151 const runCmd = process . env . WIN95_PROBE_RUN ;
82152 const runAfter = process . env . WIN95_PROBE_RUN_AFTER ;
83- let scriptArmed = ! ! scriptCmd || ! ! runCmd ;
153+ // WIN95_PROBE_DOSBOX=1 → after desktop, open COMMAND.COM, type `dir`,
154+ // optionally Alt+Enter to fullscreen. Regression test for the windowed
155+ // DOS box clobbering VBE (felixrieseberg/v86 vga-defer-vbe-disable-v86).
156+ const dosBox = process . env . WIN95_PROBE_DOSBOX === "1" ;
157+ const wantVgaTrace = process . env . WIN95_PROBE_VGATRACE === "1" ;
158+ let scriptArmed = ! ! scriptCmd || ! ! runCmd || dosBox ;
84159
85160 const tick = ( ) => {
86161 try {
162+ if ( wantVgaTrace && ! vgaTrace ) armVgaTrace ( emulator ) ;
87163 const s = collectStatus ( emulator ) ;
88164 fs . writeFileSync ( STATUS_FILE , JSON . stringify ( s , null , 2 ) ) ;
89165
@@ -98,11 +174,46 @@ export function startProbe(emulator: any) {
98174 }
99175 } catch { }
100176
177+ dumpVgaTrace ( emulator ) ;
178+
101179 // Once at desktop, fire the keyboard script (once). The 8s settle is
102180 // for the "Welcome to Windows 95" tip dialog to be dismissable —
103181 // we send Esc first to clear it.
104182 if ( scriptArmed && s . phase === "desktop" && s . uptimeSec > 8 ) {
105183 scriptArmed = false ;
184+ if ( dosBox ) {
185+ console . log ( "[probe] desktop detected, opening DOS box" ) ;
186+ runScript ( emulator , [
187+ { type : "wait" , ms : 3000 } ,
188+ { type : "keys" , dn : SC . ESC_DN , up : SC . ESC_UP } ,
189+ { type : "wait" , ms : 1000 } ,
190+ { type : "keys" , dn : SC . ESC_DN , up : SC . ESC_UP } ,
191+ { type : "wait" , ms : 1000 } ,
192+ { type : "chord" , keys : [
193+ { dn : SC . CTRL_DN , up : SC . CTRL_UP } ,
194+ { dn : SC . ESC_DN , up : SC . ESC_UP } ,
195+ ] } ,
196+ { type : "wait" , ms : 1200 } ,
197+ { type : "keys" , dn : SC . R_DN , up : SC . R_UP } ,
198+ { type : "wait" , ms : 1000 } ,
199+ { type : "text" , text : "command" } ,
200+ { type : "wait" , ms : 400 } ,
201+ { type : "keys" , dn : SC . ENTER_DN , up : SC . ENTER_UP } ,
202+ { type : "wait" , ms : 5000 } ,
203+ { type : "text" , text : "dir" } ,
204+ { type : "wait" , ms : 200 } ,
205+ { type : "keys" , dn : SC . ENTER_DN , up : SC . ENTER_UP } ,
206+ { type : "wait" , ms : 3000 } ,
207+ ...( process . env . WIN95_PROBE_DOSBOX_ALTENTER === "1" ? [
208+ { type : "chord" , keys : [
209+ { dn : SC . ALT_DN , up : SC . ALT_UP } ,
210+ { dn : SC . ENTER_DN , up : SC . ENTER_UP } ,
211+ ] } ,
212+ { type : "wait" , ms : 4000 } ,
213+ ] : [ ] ) ,
214+ ] ) ;
215+ return ;
216+ }
106217 if ( runCmd ) {
107218 console . log ( "[probe] desktop detected, Run →" , runCmd ) ;
108219 runScript ( emulator , [
@@ -128,7 +239,8 @@ export function startProbe(emulator: any) {
128239 { type : "keys" , dn : SC . ENTER_DN , up : SC . ENTER_UP } ,
129240 ] : [ ] ) ,
130241 ] ) ;
131- } else {
242+ return ;
243+ }
132244 console . log ( "[probe] desktop detected, running script:" , scriptCmd ) ;
133245 runScript ( emulator , [
134246 { type : "wait" , ms : 3000 } ,
@@ -160,7 +272,6 @@ export function startProbe(emulator: any) {
160272 { type : "wait" , ms : 400 } ,
161273 { type : "keys" , dn : SC . ENTER_DN , up : SC . ENTER_UP } ,
162274 ] ) ;
163- }
164275 }
165276
166277 if ( s . verdict ) {
@@ -273,7 +384,7 @@ function collectStatus(emulator: any): ProbeStatus {
273384 // Made it to ≥640×480 graphics → desktop reached. But if a keyboard
274385 // script is running, hold off — the outer harness reads the SMB log
275386 // directly and we just keep the app alive.
276- else if ( atDesktop && uptimeSec > 30 && ! process . env . WIN95_PROBE_SCRIPT && ! process . env . WIN95_PROBE_RUN ) verdict = "SUCCESS" ;
387+ else if ( atDesktop && uptimeSec > 30 && ! process . env . WIN95_PROBE_SCRIPT && ! process . env . WIN95_PROBE_RUN && ! process . env . WIN95_PROBE_DOSBOX ) verdict = "SUCCESS" ;
277388 // Timeout
278389 else if ( uptimeSec > 180 ) verdict = "FAIL_OTHER" ;
279390
0 commit comments