@aptre/v86 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. package/src/virtio_net.ts +326 -0
@@ -0,0 +1,1688 @@
1
+ declare let DEBUG: boolean
2
+
3
+ import { v86 } from '../main.js'
4
+ import { LOG_CPU, WASM_TABLE_OFFSET, WASM_TABLE_SIZE } from '../const.js'
5
+ import { get_rand_int, load_file, read_sized_string_from_mem } from '../lib.js'
6
+ import { dbg_assert, dbg_trace, dbg_log, set_log_level } from '../log.js'
7
+ import * as print_stats from './print_stats.js'
8
+ import { Bus } from '../bus.js'
9
+ import {
10
+ BOOT_ORDER_FD_FIRST,
11
+ BOOT_ORDER_HD_FIRST,
12
+ BOOT_ORDER_CD_FIRST,
13
+ } from '../rtc.js'
14
+ import { SpeakerAdapter } from './speaker.js'
15
+ import { NetworkAdapter } from './network.js'
16
+ import { FetchNetworkAdapter } from './fetch_network.js'
17
+ import { WispNetworkAdapter } from './wisp_network.js'
18
+ import { KeyboardAdapter } from './keyboard.js'
19
+ import { MouseAdapter } from './mouse.js'
20
+ import { ScreenAdapter } from './screen.js'
21
+ import { DummyScreenAdapter } from './dummy_screen.js'
22
+ import {
23
+ SerialAdapter,
24
+ VirtioConsoleAdapter,
25
+ SerialAdapterXtermJS,
26
+ VirtioConsoleAdapterXtermJS,
27
+ } from './serial.js'
28
+ import { InBrowserNetworkAdapter } from './inbrowser_network.js'
29
+
30
+ import {
31
+ FileStorageInterface,
32
+ MemoryFileStorage,
33
+ ServerFileStorageWrapper,
34
+ } from './filestorage.js'
35
+ import { SyncBuffer, buffer_from_object } from '../buffer.js'
36
+ import { FS } from '../../lib/filesystem.js'
37
+
38
+ type WasmExports = any
39
+
40
+ type EmulatorSettings = any
41
+
42
+ type V86Options = any
43
+
44
+ type FileDescriptor = any
45
+
46
+ type AutoStep = any
47
+
48
+ class FileExistsError {
49
+ message: string
50
+ constructor(message?: string) {
51
+ this.message = message || 'File already exists'
52
+ }
53
+ }
54
+ FileExistsError.prototype = Error.prototype
55
+
56
+ class FileNotFoundError {
57
+ message: string
58
+ constructor(message?: string) {
59
+ this.message = message || 'File not found'
60
+ }
61
+ }
62
+ FileNotFoundError.prototype = Error.prototype
63
+
64
+ /**
65
+ * Constructor for emulator instances.
66
+ *
67
+ * For API usage, see v86.d.ts in the root of this repository.
68
+ */
69
+ export class V86 {
70
+ cpu_is_running = false
71
+
72
+ cpu_exception_hook: (n: number) => void = function (_n: number) {}
73
+
74
+ bus: any
75
+
76
+ emulator_bus: any
77
+
78
+ v86: any
79
+
80
+ wasm_source: any
81
+
82
+ zstd_worker: Worker | null = null
83
+ zstd_worker_request_id = 0
84
+
85
+ zstd_context: any = null
86
+
87
+ keyboard_adapter: any
88
+
89
+ mouse_adapter: any
90
+
91
+ screen_adapter: any
92
+
93
+ network_adapter: any
94
+
95
+ serial_adapter: any
96
+
97
+ speaker_adapter: any
98
+
99
+ virtio_console_adapter: any
100
+
101
+ fs9p: any
102
+
103
+ constructor(options: V86Options) {
104
+ if (typeof options.log_level === 'number') {
105
+ // XXX: Shared between all emulator instances
106
+ set_log_level(options.log_level)
107
+ }
108
+
109
+ //var worker = new Worker("src/browser/worker.js");
110
+ //var adapter_bus = this.bus = WorkerBus.init(worker);
111
+
112
+ const bus = Bus.create()
113
+ this.bus = bus[0]
114
+ this.emulator_bus = bus[1]
115
+
116
+ let cpu: any
117
+
118
+ let wasm_memory: any
119
+
120
+ const wasm_table = new WebAssembly.Table({
121
+ element: 'anyfunc',
122
+ initial: WASM_TABLE_SIZE + WASM_TABLE_OFFSET,
123
+ })
124
+
125
+ const wasm_shared_funcs = {
126
+ cpu_exception_hook: (n: number) => this.cpu_exception_hook(n),
127
+
128
+ run_hardware_timers: function (a: any, t: any) {
129
+ return cpu.run_hardware_timers(a, t)
130
+ },
131
+ cpu_event_halt: () => {
132
+ this.emulator_bus.send('cpu-event-halt')
133
+ },
134
+ abort: function () {
135
+ dbg_assert(false)
136
+ },
137
+ microtick: v86.microtick,
138
+ get_rand_int: function () {
139
+ return get_rand_int()
140
+ },
141
+ stop_idling: function () {
142
+ return cpu.stop_idling()
143
+ },
144
+
145
+ io_port_read8: function (addr: number) {
146
+ return cpu.io.port_read8(addr)
147
+ },
148
+ io_port_read16: function (addr: number) {
149
+ return cpu.io.port_read16(addr)
150
+ },
151
+ io_port_read32: function (addr: number) {
152
+ return cpu.io.port_read32(addr)
153
+ },
154
+ io_port_write8: function (addr: number, value: number) {
155
+ cpu.io.port_write8(addr, value)
156
+ },
157
+ io_port_write16: function (addr: number, value: number) {
158
+ cpu.io.port_write16(addr, value)
159
+ },
160
+ io_port_write32: function (addr: number, value: number) {
161
+ cpu.io.port_write32(addr, value)
162
+ },
163
+
164
+ mmap_read8: function (addr: number) {
165
+ return cpu.mmap_read8(addr)
166
+ },
167
+ mmap_read32: function (addr: number) {
168
+ return cpu.mmap_read32(addr)
169
+ },
170
+ mmap_write8: function (addr: number, value: number) {
171
+ cpu.mmap_write8(addr, value)
172
+ },
173
+ mmap_write16: function (addr: number, value: number) {
174
+ cpu.mmap_write16(addr, value)
175
+ },
176
+ mmap_write32: function (addr: number, value: number) {
177
+ cpu.mmap_write32(addr, value)
178
+ },
179
+ mmap_write64: function (
180
+ addr: number,
181
+ value0: number,
182
+ value1: number,
183
+ ) {
184
+ cpu.mmap_write64(addr, value0, value1)
185
+ },
186
+ mmap_write128: function (
187
+ addr: number,
188
+ value0: number,
189
+ value1: number,
190
+ value2: number,
191
+ value3: number,
192
+ ) {
193
+ cpu.mmap_write128(addr, value0, value1, value2, value3)
194
+ },
195
+
196
+ log_from_wasm: function (offset: number, len: number) {
197
+ const str = read_sized_string_from_mem(wasm_memory, offset, len)
198
+ dbg_log(str, LOG_CPU)
199
+ },
200
+ console_log_from_wasm: function (offset: number, len: number) {
201
+ const str = read_sized_string_from_mem(wasm_memory, offset, len)
202
+ console.error(str)
203
+ },
204
+ dbg_trace_from_wasm: function () {
205
+ dbg_trace(LOG_CPU)
206
+ },
207
+
208
+ codegen_finalize: (
209
+ wasm_table_index: number,
210
+ start: number,
211
+ state_flags: number,
212
+ ptr: number,
213
+ len: number,
214
+ ) => {
215
+ cpu.codegen_finalize(
216
+ wasm_table_index,
217
+ start,
218
+ state_flags,
219
+ ptr,
220
+ len,
221
+ )
222
+ },
223
+ jit_clear_func: (wasm_table_index: number) =>
224
+ cpu.jit_clear_func(wasm_table_index),
225
+ jit_clear_all_funcs: () => cpu.jit_clear_all_funcs(),
226
+
227
+ __indirect_function_table: wasm_table,
228
+ }
229
+
230
+ let wasm_fn = options.wasm_fn
231
+
232
+ if (!wasm_fn) {
233
+ wasm_fn = (env: any) => {
234
+ /* global __dirname */
235
+
236
+ return new Promise((resolve) => {
237
+ let v86_bin = DEBUG ? 'v86-debug.wasm' : 'v86.wasm'
238
+ let v86_bin_fallback = 'v86-fallback.wasm'
239
+
240
+ if (options.wasm_path) {
241
+ v86_bin = options.wasm_path
242
+ v86_bin_fallback = v86_bin.replace(
243
+ 'v86.wasm',
244
+ 'v86-fallback.wasm',
245
+ )
246
+ } else if (
247
+ typeof window === 'undefined' &&
248
+ typeof __dirname === 'string'
249
+ ) {
250
+ v86_bin = __dirname + '/' + v86_bin
251
+ v86_bin_fallback = __dirname + '/' + v86_bin_fallback
252
+ } else {
253
+ v86_bin = 'build/' + v86_bin
254
+ v86_bin_fallback = 'build/' + v86_bin_fallback
255
+ }
256
+
257
+ load_file(v86_bin, {
258
+ done: async (bytes: any) => {
259
+ try {
260
+ const { instance } =
261
+ await WebAssembly.instantiate(bytes, env)
262
+ this.wasm_source = bytes
263
+ resolve(instance.exports)
264
+ } catch {
265
+ load_file(v86_bin_fallback, {
266
+ done: async (bytes: any) => {
267
+ const { instance } =
268
+ await WebAssembly.instantiate(
269
+ bytes,
270
+ env,
271
+ )
272
+ this.wasm_source = bytes
273
+ resolve(instance.exports)
274
+ },
275
+ })
276
+ }
277
+ },
278
+
279
+ progress: (e: any) => {
280
+ this.emulator_bus.send('download-progress', {
281
+ file_index: 0,
282
+ file_count: 1,
283
+ file_name: v86_bin,
284
+
285
+ lengthComputable: e.lengthComputable,
286
+ total: e.total,
287
+ loaded: e.loaded,
288
+ })
289
+ },
290
+ })
291
+ })
292
+ }
293
+ }
294
+
295
+ wasm_fn({ env: wasm_shared_funcs }).then((exports: WasmExports) => {
296
+ wasm_memory = exports.memory
297
+ exports['rust_init']()
298
+
299
+ const emulator = (this.v86 = new v86(this.emulator_bus, {
300
+ exports,
301
+ wasm_table,
302
+ }))
303
+ cpu = emulator.cpu
304
+
305
+ this.continue_init(emulator, options)
306
+ })
307
+ }
308
+
309
+ private async continue_init(
310
+ emulator: any,
311
+ options: V86Options,
312
+ ): Promise<void> {
313
+ this.bus.register(
314
+ 'emulator-stopped',
315
+ function (this: V86) {
316
+ this.cpu_is_running = false
317
+ this.screen_adapter.pause()
318
+ },
319
+ this,
320
+ )
321
+
322
+ this.bus.register(
323
+ 'emulator-started',
324
+ function (this: V86) {
325
+ this.cpu_is_running = true
326
+ this.screen_adapter.continue()
327
+ },
328
+ this,
329
+ )
330
+
331
+ const settings: EmulatorSettings = {}
332
+
333
+ const boot_order = options.boot_order
334
+ ? options.boot_order
335
+ : options.fda
336
+ ? BOOT_ORDER_FD_FIRST
337
+ : options.hda
338
+ ? BOOT_ORDER_HD_FIRST
339
+ : BOOT_ORDER_CD_FIRST
340
+
341
+ settings.acpi = options.acpi
342
+ settings.disable_jit = options.disable_jit
343
+ settings.load_devices = true
344
+ settings.memory_size = options.memory_size || 64 * 1024 * 1024
345
+ settings.vga_memory_size = options.vga_memory_size || 8 * 1024 * 1024
346
+ settings.boot_order = boot_order
347
+ settings.fastboot = options.fastboot || false
348
+ settings.fda = undefined
349
+ settings.fdb = undefined
350
+ settings.uart1 = options.uart1
351
+ settings.uart2 = options.uart2
352
+ settings.uart3 = options.uart3
353
+ settings.cmdline = options.cmdline
354
+ settings.preserve_mac_from_state_image =
355
+ options.preserve_mac_from_state_image
356
+ settings.mac_address_translation = options.mac_address_translation
357
+ settings.cpuid_level = options.cpuid_level
358
+ settings.virtio_balloon = options.virtio_balloon
359
+ settings.virtio_console = !!options.virtio_console
360
+
361
+ const relay_url =
362
+ options.network_relay_url ||
363
+ (options.net_device && options.net_device.relay_url)
364
+ if (relay_url) {
365
+ // TODO: remove bus, use direct calls instead
366
+ if (relay_url === 'fetch') {
367
+ this.network_adapter = new FetchNetworkAdapter(
368
+ this.bus,
369
+ options.net_device,
370
+ )
371
+ } else if (relay_url === 'inbrowser') {
372
+ // NOTE: experimental, will change when usage of options.net_device gets refactored in favour of emulator.bus
373
+ this.network_adapter = new InBrowserNetworkAdapter(
374
+ this.bus,
375
+ options.net_device,
376
+ )
377
+ } else if (
378
+ relay_url.startsWith('wisp://') ||
379
+ relay_url.startsWith('wisps://')
380
+ ) {
381
+ this.network_adapter = new WispNetworkAdapter(
382
+ relay_url,
383
+ this.bus,
384
+ options.net_device,
385
+ )
386
+ } else {
387
+ this.network_adapter = new NetworkAdapter(relay_url, this.bus)
388
+ }
389
+ }
390
+
391
+ // Enable unconditionally, so that state images don't miss hardware
392
+ // TODO: Should be properly fixed in restore_state
393
+ settings.net_device = options.net_device || { type: 'ne2k' }
394
+
395
+ const screen_options = options.screen || {}
396
+ if (options.screen_container) {
397
+ screen_options.container = options.screen_container
398
+ }
399
+
400
+ if (!options.disable_keyboard) {
401
+ this.keyboard_adapter = new KeyboardAdapter(this.bus)
402
+ }
403
+ if (!options.disable_mouse) {
404
+ this.mouse_adapter = new MouseAdapter(
405
+ this.bus,
406
+ screen_options.container,
407
+ )
408
+ }
409
+
410
+ if (screen_options.container) {
411
+ this.screen_adapter = new ScreenAdapter(
412
+ screen_options,
413
+ () =>
414
+ this.v86.cpu.devices.vga &&
415
+ this.v86.cpu.devices.vga.screen_fill_buffer(),
416
+ )
417
+ } else {
418
+ this.screen_adapter = new DummyScreenAdapter(screen_options)
419
+ }
420
+ settings.screen = this.screen_adapter
421
+ settings.screen_options = screen_options
422
+
423
+ settings.serial_console = options.serial_console || { type: 'none' }
424
+
425
+ // NOTE: serial_container_xtermjs and serial_container are deprecated
426
+ if (options.serial_container_xtermjs) {
427
+ settings.serial_console.type = 'xtermjs'
428
+ settings.serial_console.container = options.serial_container_xtermjs
429
+ } else if (options.serial_container) {
430
+ settings.serial_console.type = 'textarea'
431
+ settings.serial_console.container = options.serial_container
432
+ }
433
+
434
+ if (settings.serial_console?.type === 'xtermjs') {
435
+ const xterm_lib =
436
+ settings.serial_console.xterm_lib || (window as any)['Terminal']
437
+ this.serial_adapter = new SerialAdapterXtermJS(
438
+ settings.serial_console.container,
439
+ this.bus,
440
+ xterm_lib,
441
+ )
442
+ } else if (settings.serial_console?.type === 'textarea') {
443
+ this.serial_adapter = new SerialAdapter(
444
+ settings.serial_console.container,
445
+ this.bus,
446
+ )
447
+ //this.recording_adapter = new SerialRecordingAdapter(this.bus);
448
+ }
449
+
450
+ const virtio_console_settings =
451
+ options.virtio_console &&
452
+ typeof options.virtio_console === 'boolean'
453
+ ? { type: 'none' }
454
+ : options.virtio_console
455
+
456
+ if (virtio_console_settings?.type === 'xtermjs') {
457
+ const xterm_lib =
458
+ virtio_console_settings.xterm_lib || (window as any)['Terminal']
459
+ this.virtio_console_adapter = new VirtioConsoleAdapterXtermJS(
460
+ virtio_console_settings.container,
461
+ this.bus,
462
+ xterm_lib,
463
+ )
464
+ } else if (virtio_console_settings?.type === 'textarea') {
465
+ this.virtio_console_adapter = new VirtioConsoleAdapter(
466
+ virtio_console_settings.container,
467
+ this.bus,
468
+ )
469
+ }
470
+
471
+ if (!options.disable_speaker) {
472
+ this.speaker_adapter = new SpeakerAdapter(this.bus)
473
+ }
474
+
475
+ // ugly, but required for closure compiler compilation
476
+
477
+ function put_on_settings(name: string, buffer: any) {
478
+ switch (name) {
479
+ case 'hda':
480
+ settings.hda = buffer
481
+ break
482
+ case 'hdb':
483
+ settings.hdb = buffer
484
+ break
485
+ case 'cdrom':
486
+ settings.cdrom = buffer
487
+ break
488
+ case 'fda':
489
+ settings.fda = buffer
490
+ break
491
+ case 'fdb':
492
+ settings.fdb = buffer
493
+ break
494
+
495
+ case 'multiboot':
496
+ settings.multiboot = buffer.buffer
497
+ break
498
+ case 'bzimage':
499
+ settings.bzimage = buffer.buffer
500
+ break
501
+ case 'initrd':
502
+ settings.initrd = buffer.buffer
503
+ break
504
+
505
+ case 'bios':
506
+ settings.bios = buffer.buffer
507
+ break
508
+ case 'vga_bios':
509
+ settings.vga_bios = buffer.buffer
510
+ break
511
+ case 'initial_state':
512
+ settings.initial_state = buffer.buffer
513
+ break
514
+ case 'fs9p_json':
515
+ settings.fs9p_json = buffer
516
+ break
517
+ default:
518
+ dbg_assert(false, name)
519
+ }
520
+ }
521
+
522
+ const files_to_load: any[] = []
523
+
524
+ const add_file = (name: string, file: any) => {
525
+ if (!file) {
526
+ return
527
+ }
528
+
529
+ if (file.get && file.set && file.load) {
530
+ files_to_load.push({
531
+ name: name,
532
+ loadable: file,
533
+ })
534
+ return
535
+ }
536
+
537
+ if (
538
+ name === 'bios' ||
539
+ name === 'vga_bios' ||
540
+ name === 'initial_state' ||
541
+ name === 'multiboot' ||
542
+ name === 'bzimage' ||
543
+ name === 'initrd'
544
+ ) {
545
+ // Ignore async for these because they must be available before boot.
546
+ // This should make result.buffer available after the object is loaded
547
+ file.async = false
548
+ }
549
+
550
+ if (name === 'fda' || name === 'fdb') {
551
+ // small, doesn't make sense loading asynchronously
552
+ file.async = false
553
+ }
554
+
555
+ if (file.url && !file.async) {
556
+ files_to_load.push({
557
+ name: name,
558
+ url: file.url,
559
+ size: file.size,
560
+ })
561
+ } else {
562
+ files_to_load.push({
563
+ name,
564
+ loadable: buffer_from_object(
565
+ file,
566
+ this.zstd_decompress_worker.bind(this),
567
+ ),
568
+ })
569
+ }
570
+ }
571
+
572
+ if (options.state) {
573
+ console.warn(
574
+ "Warning: Unknown option 'state'. Did you mean 'initial_state'?",
575
+ )
576
+ }
577
+
578
+ add_file('bios', options.bios)
579
+ add_file('vga_bios', options.vga_bios)
580
+ add_file('cdrom', options.cdrom)
581
+ add_file('hda', options.hda)
582
+ add_file('hdb', options.hdb)
583
+ add_file('fda', options.fda)
584
+ add_file('fdb', options.fdb)
585
+ add_file('initial_state', options.initial_state)
586
+ add_file('multiboot', options.multiboot)
587
+ add_file('bzimage', options.bzimage)
588
+ add_file('initrd', options.initrd)
589
+
590
+ if (options.filesystem && options.filesystem.handle9p) {
591
+ settings.handle9p = options.filesystem.handle9p
592
+ } else if (options.filesystem && options.filesystem.proxy_url) {
593
+ settings.proxy9p = options.filesystem.proxy_url
594
+ } else if (options.filesystem) {
595
+ let fs_url = options.filesystem.basefs
596
+ const base_url = options.filesystem.baseurl
597
+
598
+ let file_storage: FileStorageInterface = new MemoryFileStorage()
599
+
600
+ if (base_url) {
601
+ file_storage = new ServerFileStorageWrapper(
602
+ file_storage,
603
+ base_url,
604
+ this.zstd_decompress.bind(this),
605
+ )
606
+ }
607
+ settings.fs9p = this.fs9p = new FS(file_storage)
608
+
609
+ if (fs_url) {
610
+ dbg_assert(base_url, 'Filesystem: baseurl must be specified')
611
+
612
+ let size: number | undefined
613
+
614
+ if (typeof fs_url === 'object') {
615
+ size = fs_url.size
616
+ fs_url = fs_url.url
617
+ }
618
+ dbg_assert(typeof fs_url === 'string')
619
+
620
+ files_to_load.push({
621
+ name: 'fs9p_json',
622
+ url: fs_url,
623
+ size: size,
624
+ as_json: true,
625
+ })
626
+ }
627
+ }
628
+
629
+ const total = files_to_load.length
630
+
631
+ const cont = (index: number) => {
632
+ if (index === total) {
633
+ setTimeout(() => done.call(this), 0)
634
+ return
635
+ }
636
+
637
+ const f = files_to_load[index]
638
+
639
+ if (f.loadable) {
640
+ f.loadable.onload = () => {
641
+ put_on_settings(f.name, f.loadable)
642
+ cont(index + 1)
643
+ }
644
+ f.loadable.load()
645
+ } else {
646
+ load_file(f.url, {
647
+ done: (result: any) => {
648
+ if (
649
+ f.url.endsWith('.zst') &&
650
+ f.name !== 'initial_state'
651
+ ) {
652
+ dbg_assert(
653
+ f.size,
654
+ 'A size must be provided for compressed images',
655
+ )
656
+ result = this.zstd_decompress(
657
+ f.size,
658
+ new Uint8Array(result),
659
+ )
660
+ }
661
+
662
+ put_on_settings(
663
+ f.name,
664
+ f.as_json ? result : new SyncBuffer(result),
665
+ )
666
+ cont(index + 1)
667
+ },
668
+
669
+ progress: (e: any) => {
670
+ if (e.target.status === 200) {
671
+ this.emulator_bus.send('download-progress', {
672
+ file_index: index,
673
+ file_count: total,
674
+ file_name: f.url,
675
+
676
+ lengthComputable: e.lengthComputable,
677
+ total: e.total || f.size,
678
+ loaded: e.loaded,
679
+ })
680
+ } else {
681
+ this.emulator_bus.send('download-error', {
682
+ file_index: index,
683
+ file_count: total,
684
+ file_name: f.url,
685
+ request: e.target,
686
+ })
687
+ }
688
+ },
689
+ as_json: f.as_json,
690
+ })
691
+ }
692
+ }
693
+ cont(0)
694
+
695
+ const done = async function (this: V86) {
696
+ //if(settings.initial_state)
697
+ //{
698
+ // // avoid large allocation now, memory will be restored later anyway
699
+ // settings.memory_size = 0;
700
+ //}
701
+
702
+ if (settings.fs9p && settings.fs9p_json) {
703
+ if (!settings.initial_state) {
704
+ settings.fs9p.load_from_json(settings.fs9p_json)
705
+
706
+ if (options.bzimage_initrd_from_filesystem) {
707
+ const { bzimage_path, initrd_path } =
708
+ this.get_bzimage_initrd_from_filesystem(
709
+ settings.fs9p,
710
+ )
711
+
712
+ dbg_log(
713
+ 'Found bzimage: ' +
714
+ bzimage_path +
715
+ ' and initrd: ' +
716
+ initrd_path,
717
+ )
718
+
719
+ const [initrd, bzimage] = await Promise.all([
720
+ settings.fs9p.read_file(initrd_path),
721
+ settings.fs9p.read_file(bzimage_path),
722
+ ])
723
+ put_on_settings('initrd', new SyncBuffer(initrd.buffer))
724
+ put_on_settings(
725
+ 'bzimage',
726
+ new SyncBuffer(bzimage.buffer),
727
+ )
728
+ }
729
+ } else {
730
+ dbg_log(
731
+ 'Filesystem basefs ignored: Overridden by state image',
732
+ )
733
+ }
734
+ } else {
735
+ dbg_assert(
736
+ !options.bzimage_initrd_from_filesystem ||
737
+ settings.initial_state,
738
+ 'bzimage_initrd_from_filesystem: Requires a filesystem',
739
+ )
740
+ }
741
+
742
+ if (this.serial_adapter && this.serial_adapter.show) {
743
+ this.serial_adapter.show()
744
+ }
745
+ if (
746
+ this.virtio_console_adapter &&
747
+ this.virtio_console_adapter.show
748
+ ) {
749
+ this.virtio_console_adapter.show()
750
+ }
751
+
752
+ this.v86.init(settings)
753
+
754
+ if (settings.initial_state) {
755
+ emulator.restore_state(settings.initial_state)
756
+
757
+ // The GC can't free settings, since it is referenced from
758
+ // several closures. This isn't needed anymore, so we delete it
759
+ // here
760
+ settings.initial_state = undefined
761
+ }
762
+
763
+ if (options.autostart) {
764
+ this.v86.run()
765
+ }
766
+
767
+ this.emulator_bus.send('emulator-loaded')
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Decompress zstd data synchronously.
773
+ */
774
+ zstd_decompress(decompressed_size: number, src: Uint8Array): ArrayBuffer {
775
+ const cpu = this.v86.cpu
776
+
777
+ dbg_assert(!this.zstd_context)
778
+ this.zstd_context = cpu.zstd_create_ctx(src.length)
779
+
780
+ new Uint8Array(cpu.wasm_memory.buffer).set(
781
+ src,
782
+ cpu.zstd_get_src_ptr(this.zstd_context),
783
+ )
784
+
785
+ const ptr = cpu.zstd_read(this.zstd_context, decompressed_size)
786
+ const result = cpu.wasm_memory.buffer.slice(
787
+ ptr,
788
+ ptr + decompressed_size,
789
+ )
790
+ cpu.zstd_read_free(ptr, decompressed_size)
791
+
792
+ cpu.zstd_free_ctx(this.zstd_context)
793
+ this.zstd_context = null
794
+
795
+ return result
796
+ }
797
+
798
+ /**
799
+ * Decompress zstd data asynchronously using a web worker.
800
+ */
801
+ async zstd_decompress_worker(
802
+ decompressed_size: number,
803
+ src: Uint8Array,
804
+ ): Promise<ArrayBuffer> {
805
+ if (!this.zstd_worker) {
806
+ function the_worker() {
807
+ let wasm: any
808
+
809
+ globalThis.onmessage = function (e: any) {
810
+ if (!wasm) {
811
+ const env: Record<string, any> = Object.fromEntries(
812
+ [
813
+ 'cpu_exception_hook',
814
+ 'run_hardware_timers',
815
+ 'cpu_event_halt',
816
+ 'microtick',
817
+ 'get_rand_int',
818
+ 'stop_idling',
819
+ 'io_port_read8',
820
+ 'io_port_read16',
821
+ 'io_port_read32',
822
+ 'io_port_write8',
823
+ 'io_port_write16',
824
+ 'io_port_write32',
825
+ 'mmap_read8',
826
+ 'mmap_read32',
827
+ 'mmap_write8',
828
+ 'mmap_write16',
829
+ 'mmap_write32',
830
+ 'mmap_write64',
831
+ 'mmap_write128',
832
+ 'codegen_finalize',
833
+ 'jit_clear_func',
834
+ 'jit_clear_all_funcs',
835
+ ].map((f) => [
836
+ f,
837
+ () =>
838
+ console.error(
839
+ 'zstd worker unexpectedly called ' + f,
840
+ ),
841
+ ]),
842
+ )
843
+
844
+ env['__indirect_function_table'] =
845
+ new WebAssembly.Table({
846
+ element: 'anyfunc',
847
+ initial: 1024,
848
+ })
849
+ env['abort'] = () => {
850
+ throw new Error('zstd worker aborted')
851
+ }
852
+ env['log_from_wasm'] = env['console_log_from_wasm'] = (
853
+ off: number,
854
+ len: number,
855
+ ) => {
856
+ console.log(
857
+ read_sized_string_from_mem(
858
+ wasm.exports.memory.buffer,
859
+ off,
860
+ len,
861
+ ),
862
+ )
863
+ }
864
+ env['dbg_trace_from_wasm'] = () => console.trace()
865
+
866
+ wasm = new WebAssembly.Instance(
867
+ new WebAssembly.Module(e.data),
868
+ { env: env },
869
+ )
870
+ return
871
+ }
872
+
873
+ const { src, decompressed_size, id } = e.data
874
+ const exports = wasm.exports
875
+
876
+ const zstd_context = exports['zstd_create_ctx'](src.length)
877
+ new Uint8Array(exports.memory.buffer).set(
878
+ src,
879
+ exports['zstd_get_src_ptr'](zstd_context),
880
+ )
881
+
882
+ const ptr = exports['zstd_read'](
883
+ zstd_context,
884
+ decompressed_size,
885
+ )
886
+ const result = exports.memory.buffer.slice(
887
+ ptr,
888
+ ptr + decompressed_size,
889
+ )
890
+ exports['zstd_read_free'](ptr, decompressed_size)
891
+
892
+ exports['zstd_free_ctx'](zstd_context)
893
+
894
+ postMessage({ result, id }, [result])
895
+ }
896
+ }
897
+
898
+ const url = URL.createObjectURL(
899
+ new Blob(['(' + the_worker.toString() + ')()'], {
900
+ type: 'text/javascript',
901
+ }),
902
+ )
903
+ this.zstd_worker = new Worker(url)
904
+ URL.revokeObjectURL(url)
905
+ this.zstd_worker.postMessage(this.wasm_source, [this.wasm_source])
906
+ }
907
+
908
+ return new Promise((resolve) => {
909
+ const id = this.zstd_worker_request_id++
910
+ const done = async (e: MessageEvent) => {
911
+ if (e.data.id === id) {
912
+ this.zstd_worker!.removeEventListener('message', done)
913
+ dbg_assert(decompressed_size === e.data.result.byteLength)
914
+ resolve(e.data.result)
915
+ }
916
+ }
917
+ this.zstd_worker!.addEventListener('message', done)
918
+ this.zstd_worker!.postMessage({ src, decompressed_size, id }, [
919
+ src.buffer,
920
+ ])
921
+ })
922
+ }
923
+
924
+ get_bzimage_initrd_from_filesystem(filesystem: any): {
925
+ initrd_path: string | undefined
926
+ bzimage_path: string | undefined
927
+ } {
928
+ const root = (filesystem.read_dir('/') || []).map(
929
+ (x: string) => '/' + x,
930
+ )
931
+ const boot = (filesystem.read_dir('/boot/') || []).map(
932
+ (x: string) => '/boot/' + x,
933
+ )
934
+
935
+ let initrd_path: string | undefined
936
+ let bzimage_path: string | undefined
937
+
938
+ for (const f of ([] as string[]).concat(root, boot)) {
939
+ const old = /old/i.test(f) || /fallback/i.test(f)
940
+ const is_bzimage = /vmlinuz/i.test(f) || /bzimage/i.test(f)
941
+ const is_initrd = /initrd/i.test(f) || /initramfs/i.test(f)
942
+
943
+ if (is_bzimage && (!bzimage_path || !old)) {
944
+ bzimage_path = f
945
+ }
946
+
947
+ if (is_initrd && (!initrd_path || !old)) {
948
+ initrd_path = f
949
+ }
950
+ }
951
+
952
+ if (!initrd_path || !bzimage_path) {
953
+ console.log(
954
+ 'Failed to find bzimage or initrd in filesystem. Files:',
955
+ )
956
+ console.log(root.join(' '))
957
+ console.log(boot.join(' '))
958
+ }
959
+
960
+ return { initrd_path, bzimage_path }
961
+ }
962
+
963
+ /**
964
+ * Start emulation. Do nothing if emulator is running already. Can be asynchronous.
965
+ */
966
+ async run(): Promise<void> {
967
+ this.v86.run()
968
+ }
969
+
970
+ /**
971
+ * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
972
+ */
973
+ async stop(): Promise<void> {
974
+ if (!this.cpu_is_running) {
975
+ return
976
+ }
977
+
978
+ await new Promise<void>((resolve) => {
979
+ const listener = () => {
980
+ this.remove_listener('emulator-stopped', listener)
981
+ resolve()
982
+ }
983
+ this.add_listener('emulator-stopped', listener)
984
+ this.v86.stop()
985
+ })
986
+ }
987
+
988
+ /**
989
+ * Free resources associated with this instance.
990
+ */
991
+ async destroy(): Promise<void> {
992
+ await this.stop()
993
+
994
+ this.v86.destroy()
995
+ if (this.keyboard_adapter) {
996
+ this.keyboard_adapter.destroy()
997
+ }
998
+ if (this.network_adapter) {
999
+ this.network_adapter.destroy()
1000
+ }
1001
+ if (this.mouse_adapter) {
1002
+ this.mouse_adapter.destroy()
1003
+ }
1004
+ if (this.screen_adapter) {
1005
+ this.screen_adapter.destroy()
1006
+ }
1007
+ if (this.serial_adapter) {
1008
+ this.serial_adapter.destroy()
1009
+ }
1010
+ if (this.speaker_adapter) {
1011
+ this.speaker_adapter.destroy()
1012
+ }
1013
+ if (this.virtio_console_adapter) {
1014
+ this.virtio_console_adapter.destroy()
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * Restart (force a reboot).
1020
+ */
1021
+ restart(): void {
1022
+ this.v86.restart()
1023
+ }
1024
+
1025
+ /**
1026
+ * Add an event listener (the emulator is an event emitter).
1027
+ *
1028
+ * The callback function gets a single argument which depends on the event.
1029
+ */
1030
+ add_listener(event: string, listener: (...args: any[]) => any): void {
1031
+ this.bus.register(event, listener, this)
1032
+ }
1033
+
1034
+ /**
1035
+ * Remove an event listener.
1036
+ */
1037
+ remove_listener(event: string, listener: (...args: any[]) => any): void {
1038
+ this.bus.unregister(event, listener)
1039
+ }
1040
+
1041
+ /**
1042
+ * Restore the emulator state from the given state, which must be an
1043
+ * ArrayBuffer returned by save_state.
1044
+ *
1045
+ * Note that the state can only be restored correctly if this constructor has
1046
+ * been created with the same options as the original instance (e.g., same disk
1047
+ * images, memory size, etc.).
1048
+ *
1049
+ * Different versions of the emulator might use a different format for the
1050
+ * state buffer.
1051
+ */
1052
+ async restore_state(state: ArrayBuffer): Promise<void> {
1053
+ dbg_assert(arguments.length === 1)
1054
+ this.v86.restore_state(state)
1055
+ }
1056
+
1057
+ /**
1058
+ * Asynchronously save the current state of the emulator.
1059
+ */
1060
+ async save_state(): Promise<ArrayBuffer> {
1061
+ dbg_assert(arguments.length === 0)
1062
+ return this.v86.save_state()
1063
+ }
1064
+
1065
+ /**
1066
+ * Get the instruction counter value.
1067
+ */
1068
+ get_instruction_counter(): number {
1069
+ if (this.v86) {
1070
+ return this.v86.cpu.instruction_counter[0] >>> 0
1071
+ } else {
1072
+ // TODO: Should be handled using events
1073
+ return 0
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Check if the emulator is running.
1079
+ */
1080
+ is_running(): boolean {
1081
+ return this.cpu_is_running
1082
+ }
1083
+
1084
+ /**
1085
+ * Set the image inserted in the floppy drive. Can be changed at runtime, as
1086
+ * when physically changing the floppy disk.
1087
+ */
1088
+ async set_fda(file: FileDescriptor): Promise<void> {
1089
+ const fda = this.v86.cpu.devices.fdc.drives[0]
1090
+ if (file.url && !file.async) {
1091
+ await new Promise<void>((resolve) => {
1092
+ load_file(file.url, {
1093
+ done: (result: any) => {
1094
+ fda.insert_disk(new SyncBuffer(result))
1095
+ resolve()
1096
+ },
1097
+ })
1098
+ })
1099
+ } else {
1100
+ const image = buffer_from_object(
1101
+ file,
1102
+ this.zstd_decompress_worker.bind(this),
1103
+ )
1104
+
1105
+ ;(image as any).onload = () => {
1106
+ fda.insert_disk(image)
1107
+ }
1108
+
1109
+ await (image as any).load()
1110
+ }
1111
+ }
1112
+
1113
+ /**
1114
+ * Set the image inserted in the second floppy drive, also at runtime.
1115
+ */
1116
+ async set_fdb(file: FileDescriptor): Promise<void> {
1117
+ const fdb = this.v86.cpu.devices.fdc.drives[1]
1118
+ if (file.url && !file.async) {
1119
+ await new Promise<void>((resolve) => {
1120
+ load_file(file.url, {
1121
+ done: (result: any) => {
1122
+ fdb.insert_disk(new SyncBuffer(result))
1123
+ resolve()
1124
+ },
1125
+ })
1126
+ })
1127
+ } else {
1128
+ const image = buffer_from_object(
1129
+ file,
1130
+ this.zstd_decompress_worker.bind(this),
1131
+ )
1132
+
1133
+ ;(image as any).onload = () => {
1134
+ fdb.insert_disk(image)
1135
+ }
1136
+
1137
+ await (image as any).load()
1138
+ }
1139
+ }
1140
+
1141
+ /**
1142
+ * Eject floppy drive fda.
1143
+ */
1144
+ eject_fda(): void {
1145
+ this.v86.cpu.devices.fdc.drives[0].eject_disk()
1146
+ }
1147
+
1148
+ /**
1149
+ * Eject second floppy drive fdb.
1150
+ */
1151
+ eject_fdb(): void {
1152
+ this.v86.cpu.devices.fdc.drives[1].eject_disk()
1153
+ }
1154
+
1155
+ /**
1156
+ * Return buffer object of floppy disk of drive fda or null if the drive is empty.
1157
+ */
1158
+ get_disk_fda(): Uint8Array | null {
1159
+ return this.v86.cpu.devices.fdc.drives[0].get_buffer()
1160
+ }
1161
+
1162
+ /**
1163
+ * Return buffer object of second floppy disk of drive fdb or null if the drive is empty.
1164
+ */
1165
+ get_disk_fdb(): Uint8Array | null {
1166
+ return this.v86.cpu.devices.fdc.drives[1].get_buffer()
1167
+ }
1168
+
1169
+ /**
1170
+ * Set the image inserted in the CD-ROM drive. Can be changed at runtime, as
1171
+ * when physically changing the CD-ROM.
1172
+ */
1173
+ async set_cdrom(file: FileDescriptor): Promise<void> {
1174
+ if (file.url && !file.async) {
1175
+ load_file(file.url, {
1176
+ done: (result: any) => {
1177
+ this.v86.cpu.devices.cdrom.set_cdrom(new SyncBuffer(result))
1178
+ },
1179
+ })
1180
+ } else {
1181
+ const image = buffer_from_object(
1182
+ file,
1183
+ this.zstd_decompress_worker.bind(this),
1184
+ )
1185
+
1186
+ ;(image as any).onload = () => {
1187
+ this.v86.cpu.devices.cdrom.set_cdrom(image)
1188
+ }
1189
+
1190
+ await (image as any).load()
1191
+ }
1192
+ }
1193
+
1194
+ /**
1195
+ * Eject the CD-ROM.
1196
+ */
1197
+ eject_cdrom(): void {
1198
+ this.v86.cpu.devices.cdrom.eject()
1199
+ }
1200
+
1201
+ /**
1202
+ * Send a sequence of scan codes to the emulated PS2 controller. A list of
1203
+ * codes can be found at http://stanislavs.org/helppc/make_codes.html.
1204
+ * Do nothing if there is no keyboard controller.
1205
+ */
1206
+ async keyboard_send_scancodes(
1207
+ codes: number[],
1208
+ delay?: number,
1209
+ ): Promise<void> {
1210
+ for (let i = 0; i < codes.length; i++) {
1211
+ this.bus.send('keyboard-code', codes[i])
1212
+ if (delay)
1213
+ await new Promise((resolve) => setTimeout(resolve, delay))
1214
+ }
1215
+ }
1216
+
1217
+ /**
1218
+ * Send translated keys.
1219
+ */
1220
+ async keyboard_send_keys(codes: number[], delay?: number): Promise<void> {
1221
+ for (let i = 0; i < codes.length; i++) {
1222
+ this.keyboard_adapter.simulate_press(codes[i])
1223
+ if (delay)
1224
+ await new Promise((resolve) => setTimeout(resolve, delay))
1225
+ }
1226
+ }
1227
+
1228
+ /**
1229
+ * Send text, assuming the guest OS uses a US keyboard layout.
1230
+ */
1231
+ async keyboard_send_text(string: string, delay?: number): Promise<void> {
1232
+ for (let i = 0; i < string.length; i++) {
1233
+ this.keyboard_adapter.simulate_char(string[i])
1234
+ if (delay)
1235
+ await new Promise((resolve) => setTimeout(resolve, delay))
1236
+ }
1237
+ }
1238
+
1239
+ /**
1240
+ * Download a screenshot (returns an img element, only works in browsers).
1241
+ */
1242
+ screen_make_screenshot(): HTMLImageElement | null {
1243
+ if (this.screen_adapter) {
1244
+ return this.screen_adapter.make_screenshot()
1245
+ }
1246
+ return null
1247
+ }
1248
+
1249
+ /**
1250
+ * Set the scaling level of the emulated screen.
1251
+ */
1252
+ screen_set_scale(sx: number, sy: number): void {
1253
+ if (this.screen_adapter) {
1254
+ this.screen_adapter.set_scale(sx, sy)
1255
+ }
1256
+ }
1257
+
1258
+ /**
1259
+ * Go fullscreen (only browsers).
1260
+ */
1261
+ screen_go_fullscreen(): void {
1262
+ if (!this.screen_adapter) {
1263
+ return
1264
+ }
1265
+
1266
+ const elem = document.getElementById('screen_container')
1267
+
1268
+ if (!elem) {
1269
+ return
1270
+ }
1271
+
1272
+ // bracket notation because otherwise they get renamed by closure compiler
1273
+
1274
+ const fn =
1275
+ (elem as any)['requestFullScreen'] ||
1276
+ (elem as any)['webkitRequestFullscreen'] ||
1277
+ (elem as any)['mozRequestFullScreen'] ||
1278
+ (elem as any)['msRequestFullScreen']
1279
+
1280
+ if (fn) {
1281
+ fn.call(elem)
1282
+
1283
+ // This is necessary, because otherwise chromium keyboard doesn't work anymore.
1284
+ // Might (but doesn't seem to) break something else
1285
+ const focus_element = document.getElementsByClassName(
1286
+ 'phone_keyboard',
1287
+ )[0] as HTMLElement | undefined
1288
+ if (focus_element) {
1289
+ focus_element.focus()
1290
+ }
1291
+ }
1292
+
1293
+ try {
1294
+ ;(navigator as any).keyboard.lock()
1295
+ } catch {
1296
+ // intentionally empty
1297
+ }
1298
+
1299
+ this.lock_mouse()
1300
+ }
1301
+
1302
+ /**
1303
+ * Lock the mouse cursor: It becomes invisible and is not moved out of the
1304
+ * browser window.
1305
+ */
1306
+ async lock_mouse(): Promise<void> {
1307
+ const elem = document.body
1308
+
1309
+ try {
1310
+ await elem.requestPointerLock({
1311
+ unadjustedMovement: true,
1312
+ })
1313
+ } catch {
1314
+ // as per MDN, retry without unadjustedMovement option
1315
+ await elem.requestPointerLock()
1316
+ }
1317
+ }
1318
+
1319
+ /**
1320
+ * Enable or disable sending mouse events to the emulated PS2 controller.
1321
+ */
1322
+ mouse_set_enabled(enabled: boolean): void {
1323
+ if (this.mouse_adapter) {
1324
+ this.mouse_adapter.emu_enabled = enabled
1325
+ }
1326
+ }
1327
+
1328
+ /**
1329
+ * Alias for mouse_set_enabled.
1330
+ */
1331
+ mouse_set_status(enabled: boolean): void {
1332
+ this.mouse_set_enabled(enabled)
1333
+ }
1334
+
1335
+ /**
1336
+ * Enable or disable sending keyboard events to the emulated PS2 controller.
1337
+ */
1338
+ keyboard_set_enabled(enabled: boolean): void {
1339
+ if (this.keyboard_adapter) {
1340
+ this.keyboard_adapter.emu_enabled = enabled
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * Alias for keyboard_set_enabled.
1346
+ */
1347
+ keyboard_set_status(enabled: boolean): void {
1348
+ this.keyboard_set_enabled(enabled)
1349
+ }
1350
+
1351
+ /**
1352
+ * Send a string to the first emulated serial terminal.
1353
+ */
1354
+ serial0_send(data: string): void {
1355
+ for (let i = 0; i < data.length; i++) {
1356
+ this.bus.send('serial0-input', data.charCodeAt(i))
1357
+ }
1358
+ }
1359
+
1360
+ /**
1361
+ * Send bytes to a serial port (to be received by the emulated PC).
1362
+ */
1363
+ serial_send_bytes(serial: number, data: Uint8Array): void {
1364
+ for (let i = 0; i < data.length; i++) {
1365
+ this.bus.send('serial' + serial + '-input', data[i])
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * Set the modem status of a serial port.
1371
+ */
1372
+ serial_set_modem_status(serial: number, status: number): void {
1373
+ this.bus.send('serial' + serial + '-modem-status-input', status)
1374
+ }
1375
+
1376
+ /**
1377
+ * Set the carrier detect status of a serial port.
1378
+ */
1379
+ serial_set_carrier_detect(serial: number, status: number): void {
1380
+ this.bus.send('serial' + serial + '-carrier-detect-input', status)
1381
+ }
1382
+
1383
+ /**
1384
+ * Set the ring indicator status of a serial port.
1385
+ */
1386
+ serial_set_ring_indicator(serial: number, status: number): void {
1387
+ this.bus.send('serial' + serial + '-ring-indicator-input', status)
1388
+ }
1389
+
1390
+ /**
1391
+ * Set the data set ready status of a serial port.
1392
+ */
1393
+ serial_set_data_set_ready(serial: number, status: number): void {
1394
+ this.bus.send('serial' + serial + '-data-set-ready-input', status)
1395
+ }
1396
+
1397
+ /**
1398
+ * Set the clear to send status of a serial port.
1399
+ */
1400
+ serial_set_clear_to_send(serial: number, status: number): void {
1401
+ this.bus.send('serial' + serial + '-clear-to-send-input', status)
1402
+ }
1403
+
1404
+ /**
1405
+ * Write to a file in the 9p filesystem. Nothing happens if no filesystem has
1406
+ * been initialized.
1407
+ */
1408
+ async create_file(file: string, data: Uint8Array): Promise<void> {
1409
+ dbg_assert(arguments.length === 2)
1410
+ const fs = this.fs9p
1411
+
1412
+ if (!fs) {
1413
+ return
1414
+ }
1415
+
1416
+ const parts = file.split('/')
1417
+ const filename = parts[parts.length - 1]
1418
+
1419
+ const path_infos = fs.SearchPath(file)
1420
+ const parent_id = path_infos.parentid
1421
+ const not_found = filename === '' || parent_id === -1
1422
+
1423
+ if (!not_found) {
1424
+ await fs.CreateBinaryFile(filename, parent_id, data)
1425
+ } else {
1426
+ return Promise.reject(new FileNotFoundError())
1427
+ }
1428
+ }
1429
+
1430
+ /**
1431
+ * Read a file in the 9p filesystem. Nothing happens if no filesystem has been
1432
+ * initialized.
1433
+ */
1434
+ async read_file(file: string): Promise<Uint8Array | undefined> {
1435
+ dbg_assert(arguments.length === 1)
1436
+ const fs = this.fs9p
1437
+
1438
+ if (!fs) {
1439
+ return
1440
+ }
1441
+
1442
+ const result = await fs.read_file(file)
1443
+
1444
+ if (result) {
1445
+ return result
1446
+ } else {
1447
+ return Promise.reject(new FileNotFoundError())
1448
+ }
1449
+ }
1450
+
1451
+ /**
1452
+ * Run a sequence of automated steps.
1453
+ * @deprecated Use wait_until_vga_screen_contains etc.
1454
+ */
1455
+ automatically(steps: AutoStep[]): void {
1456
+ const run = (steps: AutoStep[]) => {
1457
+ const step = steps[0]
1458
+
1459
+ if (!step) {
1460
+ return
1461
+ }
1462
+
1463
+ const remaining_steps = steps.slice(1)
1464
+
1465
+ if (step.sleep) {
1466
+ setTimeout(() => run(remaining_steps), step.sleep * 1000)
1467
+ return
1468
+ }
1469
+
1470
+ if (step.vga_text) {
1471
+ this.wait_until_vga_screen_contains(step.vga_text).then(() =>
1472
+ run(remaining_steps),
1473
+ )
1474
+ return
1475
+ }
1476
+
1477
+ if (step.keyboard_send) {
1478
+ if (Array.isArray(step.keyboard_send)) {
1479
+ this.keyboard_send_scancodes(step.keyboard_send)
1480
+ } else {
1481
+ dbg_assert(typeof step.keyboard_send === 'string')
1482
+ this.keyboard_send_text(step.keyboard_send)
1483
+ }
1484
+
1485
+ run(remaining_steps)
1486
+ return
1487
+ }
1488
+
1489
+ if (step.call) {
1490
+ step.call()
1491
+ run(remaining_steps)
1492
+ return
1493
+ }
1494
+
1495
+ dbg_assert(false, step)
1496
+ }
1497
+
1498
+ run(steps)
1499
+ }
1500
+
1501
+ /**
1502
+ * Wait until expected text is present on the VGA text screen.
1503
+ *
1504
+ * Returns immediately if the expected text is already present on screen
1505
+ * at the time this function is called.
1506
+ *
1507
+ * An optional timeout may be specified in options.timeout_msec, returns
1508
+ * false if the timeout expires before the expected text could be detected.
1509
+ *
1510
+ * Expected text (or texts, see below) must be of type string or RegExp,
1511
+ * strings are tested against the beginning of a screen line, regular
1512
+ * expressions against the full line but may use wildcards for partial
1513
+ * matching.
1514
+ *
1515
+ * Two methods of text detection are supported depending on the type of the
1516
+ * argument expected:
1517
+ *
1518
+ * 1. If expected is a string or RegExp then the given text string or
1519
+ * regular expression may match any line on screen for this function
1520
+ * to succeed.
1521
+ *
1522
+ * 2. If expected is an array of strings and/or RegExp objects then the
1523
+ * list of expected lines must match exactly at "the bottom" of the
1524
+ * screen. The "bottom" line is the first non-empty line starting from
1525
+ * the screen's end.
1526
+ * Expected lines should not contain any trailing whitespace and/or
1527
+ * newline characters. Expecting an empty line is valid.
1528
+ *
1529
+ * Returns true on success and false when the timeout has expired.
1530
+ */
1531
+ async wait_until_vga_screen_contains(
1532
+ expected: string | RegExp | Array<string | RegExp>,
1533
+ options?: { timeout_msec?: number },
1534
+ ): Promise<boolean> {
1535
+ const match_multi = Array.isArray(expected)
1536
+ const timeout_msec = options?.timeout_msec || 0
1537
+ const changed_rows = new Set<number>()
1538
+
1539
+ const screen_put_char = (args: any) => changed_rows.add(args[0])
1540
+ const contains_expected = (
1541
+ screen_line: string,
1542
+ pattern: string | RegExp,
1543
+ ) =>
1544
+ (pattern as RegExp).test
1545
+ ? (pattern as RegExp).test(screen_line)
1546
+ : screen_line.startsWith(pattern as string)
1547
+ const screen_lines: string[] = []
1548
+
1549
+ this.add_listener('screen-put-char', screen_put_char)
1550
+
1551
+ for (const screen_line of this.screen_adapter.get_text_screen()) {
1552
+ if (match_multi) {
1553
+ screen_lines.push(screen_line.trimRight())
1554
+ } else if (
1555
+ contains_expected(screen_line, expected as string | RegExp)
1556
+ ) {
1557
+ this.remove_listener('screen-put-char', screen_put_char)
1558
+ return true
1559
+ }
1560
+ }
1561
+
1562
+ let succeeded = false
1563
+ const end = timeout_msec ? performance.now() + timeout_msec : 0
1564
+ loop: while (!end || performance.now() < end) {
1565
+ if (match_multi) {
1566
+ let screen_height = screen_lines.length
1567
+ while (
1568
+ screen_height > 0 &&
1569
+ screen_lines[screen_height - 1] === ''
1570
+ ) {
1571
+ screen_height--
1572
+ }
1573
+ const screen_offset =
1574
+ screen_height - (expected as Array<string | RegExp>).length
1575
+ if (screen_offset >= 0) {
1576
+ let matches = true
1577
+ for (
1578
+ let i = 0;
1579
+ i < (expected as Array<string | RegExp>).length &&
1580
+ matches;
1581
+ i++
1582
+ ) {
1583
+ matches = contains_expected(
1584
+ screen_lines[screen_offset + i],
1585
+ (expected as Array<string | RegExp>)[i],
1586
+ )
1587
+ }
1588
+ if (matches) {
1589
+ succeeded = true
1590
+ break
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ await new Promise((resolve) => setTimeout(resolve, 100))
1596
+
1597
+ for (const row of changed_rows) {
1598
+ const screen_line = this.screen_adapter.get_text_row(row)
1599
+ if (match_multi) {
1600
+ screen_lines[row] = screen_line.trimRight()
1601
+ } else if (
1602
+ contains_expected(screen_line, expected as string | RegExp)
1603
+ ) {
1604
+ succeeded = true
1605
+ break loop
1606
+ }
1607
+ }
1608
+ changed_rows.clear()
1609
+ }
1610
+
1611
+ this.remove_listener('screen-put-char', screen_put_char)
1612
+ return succeeded
1613
+ }
1614
+
1615
+ /**
1616
+ * Reads data from memory at specified offset.
1617
+ */
1618
+
1619
+ read_memory(offset: number, length: number): any {
1620
+ return this.v86.cpu.read_blob(offset, length)
1621
+ }
1622
+
1623
+ /**
1624
+ * Writes data to memory at specified offset.
1625
+ */
1626
+ write_memory(blob: number[] | Uint8Array, offset: number): void {
1627
+ this.v86.cpu.write_blob(blob, offset)
1628
+ }
1629
+
1630
+ /**
1631
+ * Set the serial container to an xterm.js terminal.
1632
+ */
1633
+
1634
+ set_serial_container_xtermjs(
1635
+ element: HTMLElement,
1636
+ xterm_lib: any = (window as any)['Terminal'],
1637
+ ): void {
1638
+ if (this.serial_adapter && this.serial_adapter.destroy) {
1639
+ this.serial_adapter.destroy()
1640
+ }
1641
+ this.serial_adapter = new SerialAdapterXtermJS(
1642
+ element,
1643
+ this.bus,
1644
+ xterm_lib,
1645
+ )
1646
+ this.serial_adapter.show()
1647
+ }
1648
+
1649
+ /**
1650
+ * Set the virtio console container to an xterm.js terminal.
1651
+ */
1652
+
1653
+ set_virtio_console_container_xtermjs(
1654
+ element: HTMLElement,
1655
+ xterm_lib: any = (window as any)['Terminal'],
1656
+ ): void {
1657
+ if (
1658
+ this.virtio_console_adapter &&
1659
+ this.virtio_console_adapter.destroy
1660
+ ) {
1661
+ this.virtio_console_adapter.destroy()
1662
+ }
1663
+ this.virtio_console_adapter = new VirtioConsoleAdapterXtermJS(
1664
+ element,
1665
+ this.bus,
1666
+ xterm_lib,
1667
+ )
1668
+ this.virtio_console_adapter.show()
1669
+ }
1670
+
1671
+ get_instruction_stats(): string {
1672
+ return print_stats.stats_to_string(this.v86.cpu)
1673
+ }
1674
+ }
1675
+
1676
+ declare let module: any
1677
+ declare let importScripts: any
1678
+ declare let self: any
1679
+
1680
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
1681
+ module.exports['V86'] = V86
1682
+ } else if (typeof window !== 'undefined') {
1683
+ ;(window as any)['V86'] = V86
1684
+ } else if (typeof importScripts === 'function') {
1685
+ // web worker
1686
+
1687
+ ;(self as any)['V86'] = V86
1688
+ }