@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.
- package/LICENSE +22 -0
- package/LICENSE.MIT +22 -0
- package/Readme.md +237 -0
- package/dist/v86.browser.js +26666 -0
- package/dist/v86.browser.js.map +7 -0
- package/dist/v86.js +26632 -0
- package/dist/v86.js.map +7 -0
- package/gen/generate_analyzer.ts +512 -0
- package/gen/generate_interpreter.ts +522 -0
- package/gen/generate_jit.ts +624 -0
- package/gen/rust_ast.ts +107 -0
- package/gen/util.ts +35 -0
- package/gen/x86_table.ts +1836 -0
- package/lib/9p.ts +1547 -0
- package/lib/filesystem.ts +1879 -0
- package/lib/marshall.ts +168 -0
- package/lib/softfloat/softfloat.c +32501 -0
- package/lib/zstd/zstddeclib.c +13520 -0
- package/package.json +75 -0
- package/src/acpi.ts +267 -0
- package/src/browser/dummy_screen.ts +106 -0
- package/src/browser/fake_network.ts +1771 -0
- package/src/browser/fetch_network.ts +361 -0
- package/src/browser/filestorage.ts +124 -0
- package/src/browser/inbrowser_network.ts +57 -0
- package/src/browser/keyboard.ts +564 -0
- package/src/browser/main.ts +3415 -0
- package/src/browser/mouse.ts +255 -0
- package/src/browser/network.ts +142 -0
- package/src/browser/print_stats.ts +336 -0
- package/src/browser/screen.ts +978 -0
- package/src/browser/serial.ts +316 -0
- package/src/browser/speaker.ts +1223 -0
- package/src/browser/starter.ts +1688 -0
- package/src/browser/wisp_network.ts +332 -0
- package/src/browser/worker_bus.ts +64 -0
- package/src/buffer.ts +652 -0
- package/src/bus.ts +78 -0
- package/src/const.ts +128 -0
- package/src/cpu.ts +2891 -0
- package/src/dma.ts +474 -0
- package/src/elf.ts +251 -0
- package/src/floppy.ts +1778 -0
- package/src/ide.ts +3455 -0
- package/src/io.ts +504 -0
- package/src/iso9660.ts +317 -0
- package/src/kernel.ts +250 -0
- package/src/lib.ts +645 -0
- package/src/log.ts +149 -0
- package/src/main.ts +199 -0
- package/src/ne2k.ts +1589 -0
- package/src/pci.ts +815 -0
- package/src/pit.ts +406 -0
- package/src/ps2.ts +820 -0
- package/src/rtc.ts +537 -0
- package/src/rust/analysis.rs +101 -0
- package/src/rust/codegen.rs +2660 -0
- package/src/rust/config.rs +3 -0
- package/src/rust/control_flow.rs +425 -0
- package/src/rust/cpu/apic.rs +658 -0
- package/src/rust/cpu/arith.rs +1207 -0
- package/src/rust/cpu/call_indirect.rs +2 -0
- package/src/rust/cpu/cpu.rs +4501 -0
- package/src/rust/cpu/fpu.rs +923 -0
- package/src/rust/cpu/global_pointers.rs +112 -0
- package/src/rust/cpu/instructions.rs +2486 -0
- package/src/rust/cpu/instructions_0f.rs +5261 -0
- package/src/rust/cpu/ioapic.rs +316 -0
- package/src/rust/cpu/memory.rs +351 -0
- package/src/rust/cpu/misc_instr.rs +613 -0
- package/src/rust/cpu/mod.rs +16 -0
- package/src/rust/cpu/modrm.rs +133 -0
- package/src/rust/cpu/pic.rs +402 -0
- package/src/rust/cpu/sse_instr.rs +361 -0
- package/src/rust/cpu/string.rs +701 -0
- package/src/rust/cpu/vga.rs +175 -0
- package/src/rust/cpu_context.rs +69 -0
- package/src/rust/dbg.rs +98 -0
- package/src/rust/gen/analyzer.rs +3807 -0
- package/src/rust/gen/analyzer0f.rs +3992 -0
- package/src/rust/gen/interpreter.rs +4447 -0
- package/src/rust/gen/interpreter0f.rs +5404 -0
- package/src/rust/gen/jit.rs +5080 -0
- package/src/rust/gen/jit0f.rs +5547 -0
- package/src/rust/gen/mod.rs +14 -0
- package/src/rust/jit.rs +2443 -0
- package/src/rust/jit_instructions.rs +7881 -0
- package/src/rust/js_api.rs +6 -0
- package/src/rust/leb.rs +46 -0
- package/src/rust/lib.rs +29 -0
- package/src/rust/modrm.rs +330 -0
- package/src/rust/opstats.rs +249 -0
- package/src/rust/page.rs +15 -0
- package/src/rust/paging.rs +25 -0
- package/src/rust/prefix.rs +15 -0
- package/src/rust/profiler.rs +155 -0
- package/src/rust/regs.rs +38 -0
- package/src/rust/softfloat.rs +286 -0
- package/src/rust/state_flags.rs +27 -0
- package/src/rust/wasmgen/mod.rs +2 -0
- package/src/rust/wasmgen/wasm_builder.rs +1047 -0
- package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
- package/src/rust/zstd.rs +105 -0
- package/src/sb16.ts +1928 -0
- package/src/state.ts +359 -0
- package/src/uart.ts +472 -0
- package/src/vga.ts +2791 -0
- package/src/virtio.ts +1756 -0
- package/src/virtio_balloon.ts +273 -0
- package/src/virtio_console.ts +372 -0
- 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
|
+
}
|