starter.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417
  1. "use strict";
  2. /**
  3. * Constructor for emulator instances.
  4. *
  5. * Usage: `var emulator = new V86(options);`
  6. *
  7. * Options can have the following properties (all optional, default in parenthesis):
  8. *
  9. * - `memory_size number` (16 * 1024 * 1024) - The memory size in bytes, should
  10. * be a power of 2.
  11. * - `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.
  12. *
  13. * - `autostart boolean` (false) - If emulation should be started when emulator
  14. * is ready.
  15. *
  16. * - `disable_keyboard boolean` (false) - If the keyboard should be disabled.
  17. * - `disable_mouse boolean` (false) - If the mouse should be disabled.
  18. *
  19. * - `network_relay_url string` (No network card) - The url of a server running
  20. * websockproxy. See [networking.md](networking.md). Setting this will
  21. * enable an emulated network card.
  22. *
  23. * - `bios Object` (No bios) - Either a url pointing to a bios or an
  24. * ArrayBuffer, see below.
  25. * - `vga_bios Object` (No VGA bios) - VGA bios, see below.
  26. * - `hda Object` (No hard disk) - First hard disk, see below.
  27. * - `fda Object` (No floppy disk) - First floppy disk, see below.
  28. * - `cdrom Object` (No CD) - See below.
  29. *
  30. * - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.
  31. * - `initrd Object` - A Linux ramdisk image, see below.
  32. * - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and
  33. * initrd from the specified `filesystem`.
  34. *
  35. * - `initial_state Object` (Normal boot) - An initial state to load, see
  36. * [`restore_state`](#restore_statearraybuffer-state) and below.
  37. *
  38. * - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
  39. * [filesystem.md](filesystem.md).
  40. *
  41. * - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
  42. * that will receive and send data to the emulated serial terminal.
  43. * Alternatively the serial terminal can also be accessed programatically,
  44. * see [serial.html](../examples/serial.html).
  45. *
  46. * - `screen_container HTMLElement` (No screen) - An HTMLElement. This should
  47. * have a certain structure, see [basic.html](../examples/basic.html).
  48. *
  49. * ***
  50. *
  51. * There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
  52. *
  53. * - Pass an object that has a url. Optionally, `async: true` and `size:
  54. * size_in_bytes` can be added to the object, so that sectors of the image
  55. * are loaded on demand instead of being loaded before boot (slower, but
  56. * strongly recommended for big files). In that case, the `Range: bytes=...`
  57. * header must be supported on the server.
  58. *
  59. * ```javascript
  60. * // download file before boot
  61. * bios: {
  62. * url: "bios/seabios.bin"
  63. * }
  64. * // download file sectors as requested, size is required
  65. * hda: {
  66. * url: "disk/linux.iso",
  67. * async: true,
  68. * size: 16 * 1024 * 1024
  69. * }
  70. * ```
  71. *
  72. * - Pass an `ArrayBuffer` or `File` object as `buffer` property.
  73. *
  74. * ```javascript
  75. * // use <input type=file>
  76. * bios: {
  77. * buffer: document.all.hd_image.files[0]
  78. * }
  79. * // start with empty hard disk
  80. * hda: {
  81. * buffer: new ArrayBuffer(16 * 1024 * 1024)
  82. * }
  83. * ```
  84. *
  85. * @param {{
  86. disable_mouse: (boolean|undefined),
  87. disable_keyboard: (boolean|undefined),
  88. wasm_fn: (Function|undefined),
  89. }} options
  90. * @constructor
  91. */
  92. function V86(options)
  93. {
  94. //var worker = new Worker("src/browser/worker.js");
  95. //var adapter_bus = this.bus = WorkerBus.init(worker);
  96. this.cpu_is_running = false;
  97. this.cpu_exception_hook = function(n) {};
  98. const bus = Bus.create();
  99. const adapter_bus = this.bus = bus[0];
  100. this.emulator_bus = bus[1];
  101. var cpu;
  102. var wasm_memory;
  103. const wasm_table = new WebAssembly.Table({ element: "anyfunc", initial: WASM_TABLE_SIZE + WASM_TABLE_OFFSET });
  104. const wasm_shared_funcs = {
  105. "cpu_exception_hook": n => this.cpu_exception_hook(n),
  106. "run_hardware_timers": function(a, t) { return cpu.run_hardware_timers(a, t); },
  107. "cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); },
  108. "abort": function() { dbg_assert(false); },
  109. "microtick": v86.microtick,
  110. "get_rand_int": function() { return v86util.get_rand_int(); },
  111. "apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
  112. "io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
  113. "io_port_read16": function(addr) { return cpu.io.port_read16(addr); },
  114. "io_port_read32": function(addr) { return cpu.io.port_read32(addr); },
  115. "io_port_write8": function(addr, value) { cpu.io.port_write8(addr, value); },
  116. "io_port_write16": function(addr, value) { cpu.io.port_write16(addr, value); },
  117. "io_port_write32": function(addr, value) { cpu.io.port_write32(addr, value); },
  118. "mmap_read8": function(addr) { return cpu.mmap_read8(addr); },
  119. "mmap_read16": function(addr) { return cpu.mmap_read16(addr); },
  120. "mmap_read32": function(addr) { return cpu.mmap_read32(addr); },
  121. "mmap_write8": function(addr, value) { cpu.mmap_write8(addr, value); },
  122. "mmap_write16": function(addr, value) { cpu.mmap_write16(addr, value); },
  123. "mmap_write32": function(addr, value) { cpu.mmap_write32(addr, value); },
  124. "mmap_write64": function(addr, value0, value1) { cpu.mmap_write64(addr, value0, value1); },
  125. "mmap_write128": function(addr, value0, value1, value2, value3) {
  126. cpu.mmap_write128(addr, value0, value1, value2, value3);
  127. },
  128. "log_from_wasm": function(offset, len) {
  129. const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
  130. dbg_log(str, LOG_CPU);
  131. },
  132. "console_log_from_wasm": function(offset, len) {
  133. const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
  134. console.error(str);
  135. },
  136. "dbg_trace_from_wasm": function() {
  137. dbg_trace(LOG_CPU);
  138. },
  139. "codegen_finalize": (wasm_table_index, start, state_flags, ptr, len) => {
  140. cpu.codegen_finalize(wasm_table_index, start, state_flags, ptr, len);
  141. },
  142. "jit_clear_func": (wasm_table_index) => cpu.jit_clear_func(wasm_table_index),
  143. "jit_clear_all_funcs": () => cpu.jit_clear_all_funcs(),
  144. "__indirect_function_table": wasm_table,
  145. };
  146. let wasm_fn = options.wasm_fn;
  147. if(!wasm_fn)
  148. {
  149. wasm_fn = env =>
  150. {
  151. return new Promise(resolve => {
  152. let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm";
  153. let v86_bin_fallback = "v86-fallback.wasm";
  154. if(options.wasm_path)
  155. {
  156. v86_bin = options.wasm_path;
  157. const slash = v86_bin.lastIndexOf("/");
  158. const dir = slash === -1 ? "" : v86_bin.substr(0, slash);
  159. v86_bin_fallback = dir + "/" + v86_bin_fallback;
  160. }
  161. else if(typeof window === "undefined" && typeof __dirname === "string")
  162. {
  163. v86_bin = __dirname + "/" + v86_bin;
  164. v86_bin_fallback = __dirname + "/" + v86_bin_fallback;
  165. }
  166. else
  167. {
  168. v86_bin = "build/" + v86_bin;
  169. v86_bin_fallback = "build/" + v86_bin_fallback;
  170. }
  171. v86util.load_file(v86_bin, {
  172. done: async bytes =>
  173. {
  174. try
  175. {
  176. const { instance } = await WebAssembly.instantiate(bytes, env);
  177. this.wasm_source = bytes;
  178. resolve(instance.exports);
  179. }
  180. catch(err)
  181. {
  182. v86util.load_file(v86_bin_fallback, {
  183. done: async bytes => {
  184. const { instance } = await WebAssembly.instantiate(bytes, env);
  185. this.wasm_source = bytes;
  186. resolve(instance.exports);
  187. },
  188. });
  189. }
  190. },
  191. progress: e =>
  192. {
  193. this.emulator_bus.send("download-progress", {
  194. file_index: 0,
  195. file_count: 1,
  196. file_name: v86_bin,
  197. lengthComputable: e.lengthComputable,
  198. total: e.total,
  199. loaded: e.loaded,
  200. });
  201. }
  202. });
  203. });
  204. };
  205. }
  206. wasm_fn({ "env": wasm_shared_funcs })
  207. .then((exports) => {
  208. wasm_memory = exports.memory;
  209. exports["rust_init"]();
  210. const emulator = this.v86 = new v86(this.emulator_bus, { exports, wasm_table });
  211. cpu = emulator.cpu;
  212. this.continue_init(emulator, options);
  213. });
  214. this.zstd_worker = null;
  215. this.zstd_worker_request_id = 0;
  216. }
  217. V86.prototype.continue_init = async function(emulator, options)
  218. {
  219. this.bus.register("emulator-stopped", function()
  220. {
  221. this.cpu_is_running = false;
  222. }, this);
  223. this.bus.register("emulator-started", function()
  224. {
  225. this.cpu_is_running = true;
  226. }, this);
  227. var settings = {};
  228. this.disk_images = {
  229. fda: undefined,
  230. fdb: undefined,
  231. hda: undefined,
  232. hdb: undefined,
  233. cdrom: undefined,
  234. };
  235. const boot_order =
  236. options.boot_order ? options.boot_order :
  237. options.fda ? BOOT_ORDER_FD_FIRST :
  238. options.hda ? BOOT_ORDER_HD_FIRST : BOOT_ORDER_CD_FIRST;
  239. settings.acpi = options.acpi;
  240. settings.disable_jit = options.disable_jit;
  241. settings.load_devices = true;
  242. settings.log_level = options.log_level;
  243. settings.memory_size = options.memory_size || 64 * 1024 * 1024;
  244. settings.vga_memory_size = options.vga_memory_size || 8 * 1024 * 1024;
  245. settings.boot_order = boot_order;
  246. settings.fastboot = options.fastboot || false;
  247. settings.fda = undefined;
  248. settings.fdb = undefined;
  249. settings.uart1 = options.uart1;
  250. settings.uart2 = options.uart2;
  251. settings.uart3 = options.uart3;
  252. settings.cmdline = options.cmdline;
  253. settings.preserve_mac_from_state_image = options.preserve_mac_from_state_image;
  254. settings.mac_address_translation = options.mac_address_translation;
  255. settings.cpuid_level = options.cpuid_level;
  256. settings.virtio_console = options.virtio_console;
  257. if(options.network_adapter)
  258. {
  259. this.network_adapter = options.network_adapter(this.bus);
  260. }
  261. else if(options.network_relay_url)
  262. {
  263. this.network_adapter = new NetworkAdapter(options.network_relay_url, this.bus);
  264. }
  265. // Enable unconditionally, so that state images don't miss hardware
  266. // TODO: Should be properly fixed in restore_state
  267. settings.enable_ne2k = true;
  268. if(!options.disable_keyboard)
  269. {
  270. this.keyboard_adapter = new KeyboardAdapter(this.bus);
  271. }
  272. if(!options.disable_mouse)
  273. {
  274. this.mouse_adapter = new MouseAdapter(this.bus, options.screen_container);
  275. }
  276. if(options.screen_container)
  277. {
  278. this.screen_adapter = new ScreenAdapter(options.screen_container, this.bus);
  279. }
  280. else if(options.screen_dummy)
  281. {
  282. this.screen_adapter = new DummyScreenAdapter(this.bus);
  283. }
  284. if(options.serial_container)
  285. {
  286. this.serial_adapter = new SerialAdapter(options.serial_container, this.bus);
  287. //this.recording_adapter = new SerialRecordingAdapter(this.bus);
  288. }
  289. if(options.serial_container_xtermjs)
  290. {
  291. this.serial_adapter = new SerialAdapterXtermJS(options.serial_container_xtermjs, this.bus);
  292. }
  293. if(!options.disable_speaker)
  294. {
  295. this.speaker_adapter = new SpeakerAdapter(this.bus);
  296. }
  297. // ugly, but required for closure compiler compilation
  298. function put_on_settings(name, buffer)
  299. {
  300. switch(name)
  301. {
  302. case "hda":
  303. settings.hda = this.disk_images.hda = buffer;
  304. break;
  305. case "hdb":
  306. settings.hdb = this.disk_images.hdb = buffer;
  307. break;
  308. case "cdrom":
  309. settings.cdrom = this.disk_images.cdrom = buffer;
  310. break;
  311. case "fda":
  312. settings.fda = this.disk_images.fda = buffer;
  313. break;
  314. case "fdb":
  315. settings.fdb = this.disk_images.fdb = buffer;
  316. break;
  317. case "multiboot":
  318. settings.multiboot = this.disk_images.multiboot = buffer.buffer;
  319. break;
  320. case "bzimage":
  321. settings.bzimage = this.disk_images.bzimage = buffer.buffer;
  322. break;
  323. case "initrd":
  324. settings.initrd = this.disk_images.initrd = buffer.buffer;
  325. break;
  326. case "bios":
  327. settings.bios = buffer.buffer;
  328. break;
  329. case "vga_bios":
  330. settings.vga_bios = buffer.buffer;
  331. break;
  332. case "initial_state":
  333. settings.initial_state = buffer.buffer;
  334. break;
  335. case "fs9p_json":
  336. settings.fs9p_json = buffer;
  337. break;
  338. default:
  339. dbg_assert(false, name);
  340. }
  341. }
  342. var files_to_load = [];
  343. const add_file = (name, file) =>
  344. {
  345. if(!file)
  346. {
  347. return;
  348. }
  349. if(file.get && file.set && file.load)
  350. {
  351. files_to_load.push({
  352. name: name,
  353. loadable: file,
  354. });
  355. return;
  356. }
  357. if(name === "bios" || name === "vga_bios" ||
  358. name === "initial_state" || name === "multiboot" ||
  359. name === "bzimage" || name === "initrd")
  360. {
  361. // Ignore async for these because they must be available before boot.
  362. // This should make result.buffer available after the object is loaded
  363. file.async = false;
  364. }
  365. if(file.url && !file.async)
  366. {
  367. files_to_load.push({
  368. name: name,
  369. url: file.url,
  370. size: file.size,
  371. });
  372. }
  373. else
  374. {
  375. files_to_load.push({
  376. name,
  377. loadable: v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
  378. });
  379. }
  380. };
  381. if(options.state)
  382. {
  383. console.warn("Warning: Unknown option 'state'. Did you mean 'initial_state'?");
  384. }
  385. add_file("bios", options.bios);
  386. add_file("vga_bios", options.vga_bios);
  387. add_file("cdrom", options.cdrom);
  388. add_file("hda", options.hda);
  389. add_file("hdb", options.hdb);
  390. add_file("fda", options.fda);
  391. add_file("fdb", options.fdb);
  392. add_file("initial_state", options.initial_state);
  393. add_file("multiboot", options.multiboot);
  394. add_file("bzimage", options.bzimage);
  395. add_file("initrd", options.initrd);
  396. if(options.filesystem)
  397. {
  398. var fs_url = options.filesystem.basefs;
  399. var base_url = options.filesystem.baseurl;
  400. let file_storage = new MemoryFileStorage();
  401. if(base_url)
  402. {
  403. file_storage = new ServerFileStorageWrapper(file_storage, base_url);
  404. }
  405. settings.fs9p = this.fs9p = new FS(file_storage);
  406. if(fs_url)
  407. {
  408. dbg_assert(base_url, "Filesystem: baseurl must be specified");
  409. var size;
  410. if(typeof fs_url === "object")
  411. {
  412. size = fs_url.size;
  413. fs_url = fs_url.url;
  414. }
  415. dbg_assert(typeof fs_url === "string");
  416. files_to_load.push({
  417. name: "fs9p_json",
  418. url: fs_url,
  419. size: size,
  420. as_json: true,
  421. });
  422. }
  423. }
  424. var starter = this;
  425. var total = files_to_load.length;
  426. var cont = function(index)
  427. {
  428. if(index === total)
  429. {
  430. setTimeout(done.bind(this), 0);
  431. return;
  432. }
  433. var f = files_to_load[index];
  434. if(f.loadable)
  435. {
  436. f.loadable.onload = function(e)
  437. {
  438. put_on_settings.call(this, f.name, f.loadable);
  439. cont(index + 1);
  440. }.bind(this);
  441. f.loadable.load();
  442. }
  443. else
  444. {
  445. v86util.load_file(f.url, {
  446. done: function(result)
  447. {
  448. if(f.url.endsWith(".zst") && f.name !== "initial_state")
  449. {
  450. dbg_assert(f.size, "A size must be provided for compressed images");
  451. result = this.zstd_decompress(f.size, new Uint8Array(result));
  452. }
  453. put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
  454. cont(index + 1);
  455. }.bind(this),
  456. progress: function progress(e)
  457. {
  458. if(e.target.status === 200)
  459. {
  460. starter.emulator_bus.send("download-progress", {
  461. file_index: index,
  462. file_count: total,
  463. file_name: f.url,
  464. lengthComputable: e.lengthComputable,
  465. total: e.total || f.size,
  466. loaded: e.loaded,
  467. });
  468. }
  469. else
  470. {
  471. starter.emulator_bus.send("download-error", {
  472. file_index: index,
  473. file_count: total,
  474. file_name: f.url,
  475. request: e.target,
  476. });
  477. }
  478. },
  479. as_json: f.as_json,
  480. });
  481. }
  482. }.bind(this);
  483. cont(0);
  484. async function done()
  485. {
  486. //if(settings.initial_state)
  487. //{
  488. // // avoid large allocation now, memory will be restored later anyway
  489. // settings.memory_size = 0;
  490. //}
  491. if(settings.fs9p && settings.fs9p_json)
  492. {
  493. if(!settings.initial_state)
  494. {
  495. settings.fs9p.load_from_json(settings.fs9p_json);
  496. }
  497. else
  498. {
  499. dbg_log("Filesystem basefs ignored: Overridden by state image");
  500. }
  501. if(options.bzimage_initrd_from_filesystem)
  502. {
  503. const { bzimage_path, initrd_path } = this.get_bzimage_initrd_from_filesystem(settings.fs9p);
  504. dbg_log("Found bzimage: " + bzimage_path + " and initrd: " + initrd_path);
  505. const [initrd, bzimage] = await Promise.all([
  506. settings.fs9p.read_file(initrd_path),
  507. settings.fs9p.read_file(bzimage_path),
  508. ]);
  509. put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer));
  510. put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer));
  511. finish.call(this);
  512. }
  513. else
  514. {
  515. finish.call(this);
  516. }
  517. }
  518. else
  519. {
  520. dbg_assert(
  521. !options.bzimage_initrd_from_filesystem,
  522. "bzimage_initrd_from_filesystem: Requires a filesystem");
  523. finish.call(this);
  524. }
  525. function finish()
  526. {
  527. this.serial_adapter && this.serial_adapter.show && this.serial_adapter.show();
  528. this.bus.send("cpu-init", settings);
  529. if(settings.initial_state)
  530. {
  531. emulator.restore_state(settings.initial_state);
  532. // The GC can't free settings, since it is referenced from
  533. // several closures. This isn't needed anymore, so we delete it
  534. // here
  535. settings.initial_state = undefined;
  536. }
  537. if(options.autostart)
  538. {
  539. this.bus.send("cpu-run");
  540. }
  541. this.emulator_bus.send("emulator-loaded");
  542. }
  543. }
  544. };
  545. /**
  546. * @param {number} decompressed_size
  547. * @param {Uint8Array} src
  548. * @return {ArrayBuffer}
  549. */
  550. V86.prototype.zstd_decompress = function(decompressed_size, src)
  551. {
  552. const cpu = this.v86.cpu;
  553. dbg_assert(!this.zstd_context);
  554. this.zstd_context = cpu.zstd_create_ctx(src.length);
  555. new Uint8Array(cpu.wasm_memory.buffer).set(src, cpu.zstd_get_src_ptr(this.zstd_context));
  556. const ptr = cpu.zstd_read(this.zstd_context, decompressed_size);
  557. const result = cpu.wasm_memory.buffer.slice(ptr, ptr + decompressed_size);
  558. cpu.zstd_read_free(ptr, decompressed_size);
  559. cpu.zstd_free_ctx(this.zstd_context);
  560. this.zstd_context = null;
  561. return result;
  562. };
  563. /**
  564. * @param {number} decompressed_size
  565. * @param {Uint8Array} src
  566. * @return {Promise<ArrayBuffer>}
  567. */
  568. V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
  569. {
  570. if(!this.zstd_worker)
  571. {
  572. function the_worker()
  573. {
  574. let wasm;
  575. globalThis.onmessage = function(e)
  576. {
  577. if(!wasm)
  578. {
  579. const env = Object.fromEntries([
  580. "cpu_exception_hook", "run_hardware_timers",
  581. "cpu_event_halt", "microtick", "get_rand_int",
  582. "apic_acknowledge_irq",
  583. "io_port_read8", "io_port_read16", "io_port_read32",
  584. "io_port_write8", "io_port_write16", "io_port_write32",
  585. "mmap_read8", "mmap_read16", "mmap_read32",
  586. "mmap_write8", "mmap_write16", "mmap_write32", "mmap_write64", "mmap_write128",
  587. "codegen_finalize",
  588. "jit_clear_func", "jit_clear_all_funcs",
  589. ].map(f => [f, () => console.error("zstd worker unexpectedly called " + f)]));
  590. env["__indirect_function_table"] = new WebAssembly.Table({ element: "anyfunc", initial: 1024 });
  591. env["abort"] = () => { throw new Error("zstd worker aborted"); };
  592. env["log_from_wasm"] = env["console_log_from_wasm"] = (off, len) => {
  593. console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
  594. };
  595. env["dbg_trace_from_wasm"] = () => console.trace();
  596. wasm = new WebAssembly.Instance(new WebAssembly.Module(e.data), { "env": env });
  597. return;
  598. }
  599. const { src, decompressed_size, id } = e.data;
  600. const exports = wasm.exports;
  601. const zstd_context = exports["zstd_create_ctx"](src.length);
  602. new Uint8Array(exports.memory.buffer).set(src, exports["zstd_get_src_ptr"](zstd_context));
  603. const ptr = exports["zstd_read"](zstd_context, decompressed_size);
  604. const result = exports.memory.buffer.slice(ptr, ptr + decompressed_size);
  605. exports["zstd_read_free"](ptr, decompressed_size);
  606. exports["zstd_free_ctx"](zstd_context);
  607. postMessage({ result, id }, [result]);
  608. };
  609. }
  610. const url = URL.createObjectURL(new Blob(["(" + the_worker.toString() + ")()"], { type: "text/javascript" }));
  611. this.zstd_worker = new Worker(url);
  612. URL.revokeObjectURL(url);
  613. this.zstd_worker.postMessage(this.wasm_source, [this.wasm_source]);
  614. }
  615. return new Promise(resolve => {
  616. const id = this.zstd_worker_request_id++;
  617. const done = async e =>
  618. {
  619. if(e.data.id === id)
  620. {
  621. this.zstd_worker.removeEventListener("message", done);
  622. dbg_assert(decompressed_size === e.data.result.byteLength);
  623. resolve(e.data.result);
  624. }
  625. };
  626. this.zstd_worker.addEventListener("message", done);
  627. this.zstd_worker.postMessage({ src, decompressed_size, id }, [src.buffer]);
  628. });
  629. };
  630. V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
  631. {
  632. const root = (filesystem.read_dir("/") || []).map(x => "/" + x);
  633. const boot = (filesystem.read_dir("/boot/") || []).map(x => "/boot/" + x);
  634. let initrd_path;
  635. let bzimage_path;
  636. for(let f of [].concat(root, boot))
  637. {
  638. const old = /old/i.test(f) || /fallback/i.test(f);
  639. const is_bzimage = /vmlinuz/i.test(f) || /bzimage/i.test(f);
  640. const is_initrd = /initrd/i.test(f) || /initramfs/i.test(f);
  641. if(is_bzimage && (!bzimage_path || !old))
  642. {
  643. bzimage_path = f;
  644. }
  645. if(is_initrd && (!initrd_path || !old))
  646. {
  647. initrd_path = f;
  648. }
  649. }
  650. if(!initrd_path || !bzimage_path)
  651. {
  652. console.log("Failed to find bzimage or initrd in filesystem. Files:");
  653. console.log(root.join(" "));
  654. console.log(boot.join(" "));
  655. }
  656. return { initrd_path, bzimage_path };
  657. };
  658. /**
  659. * Start emulation. Do nothing if emulator is running already. Can be
  660. * asynchronous.
  661. * @export
  662. */
  663. V86.prototype.run = async function()
  664. {
  665. this.bus.send("cpu-run");
  666. };
  667. /**
  668. * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
  669. * @export
  670. */
  671. V86.prototype.stop = async function()
  672. {
  673. if(!this.cpu_is_running)
  674. {
  675. return;
  676. }
  677. await new Promise(resolve => {
  678. const listener = () => {
  679. this.remove_listener("emulator-stopped", listener);
  680. resolve();
  681. };
  682. this.add_listener("emulator-stopped", listener);
  683. this.bus.send("cpu-stop");
  684. });
  685. };
  686. /**
  687. * @ignore
  688. * @export
  689. */
  690. V86.prototype.destroy = async function()
  691. {
  692. await this.stop();
  693. this.v86.destroy();
  694. this.keyboard_adapter && this.keyboard_adapter.destroy();
  695. this.network_adapter && this.network_adapter.destroy();
  696. this.mouse_adapter && this.mouse_adapter.destroy();
  697. this.screen_adapter && this.screen_adapter.destroy();
  698. this.serial_adapter && this.serial_adapter.destroy();
  699. this.speaker_adapter && this.speaker_adapter.destroy();
  700. };
  701. /**
  702. * Restart (force a reboot).
  703. * @export
  704. */
  705. V86.prototype.restart = function()
  706. {
  707. this.bus.send("cpu-restart");
  708. };
  709. /**
  710. * Add an event listener (the emulator is an event emitter). A list of events
  711. * can be found at [events.md](events.md).
  712. *
  713. * The callback function gets a single argument which depends on the event.
  714. *
  715. * @param {string} event Name of the event.
  716. * @param {function(*)} listener The callback function.
  717. * @export
  718. */
  719. V86.prototype.add_listener = function(event, listener)
  720. {
  721. this.bus.register(event, listener, this);
  722. };
  723. /**
  724. * Remove an event listener.
  725. *
  726. * @param {string} event
  727. * @param {function(*)} listener
  728. * @export
  729. */
  730. V86.prototype.remove_listener = function(event, listener)
  731. {
  732. this.bus.unregister(event, listener);
  733. };
  734. /**
  735. * Restore the emulator state from the given state, which must be an
  736. * ArrayBuffer returned by
  737. * [`save_state`](#save_statefunctionobject-arraybuffer-callback).
  738. *
  739. * Note that the state can only be restored correctly if this constructor has
  740. * been created with the same options as the original instance (e.g., same disk
  741. * images, memory size, etc.).
  742. *
  743. * Different versions of the emulator might use a different format for the
  744. * state buffer.
  745. *
  746. * @param {ArrayBuffer} state
  747. * @export
  748. */
  749. V86.prototype.restore_state = async function(state)
  750. {
  751. dbg_assert(arguments.length === 1);
  752. this.v86.restore_state(state);
  753. };
  754. /**
  755. * Asynchronously save the current state of the emulator.
  756. *
  757. * @return {Promise<ArrayBuffer>}
  758. * @export
  759. */
  760. V86.prototype.save_state = async function()
  761. {
  762. dbg_assert(arguments.length === 0);
  763. return this.v86.save_state();
  764. };
  765. /**
  766. * @return {number}
  767. * @ignore
  768. * @export
  769. */
  770. V86.prototype.get_instruction_counter = function()
  771. {
  772. if(this.v86)
  773. {
  774. return this.v86.cpu.instruction_counter[0] >>> 0;
  775. }
  776. else
  777. {
  778. // TODO: Should be handled using events
  779. return 0;
  780. }
  781. };
  782. /**
  783. * @return {boolean}
  784. * @export
  785. */
  786. V86.prototype.is_running = function()
  787. {
  788. return this.cpu_is_running;
  789. };
  790. /**
  791. * Set the image inserted in the floppy drive. Can be changed at runtime, as
  792. * when physically changing the floppy disk.
  793. * @export
  794. */
  795. V86.prototype.set_fda = async function(file)
  796. {
  797. if(file.url && !file.async)
  798. {
  799. v86util.load_file(file.url, {
  800. done: result =>
  801. {
  802. this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result));
  803. },
  804. });
  805. }
  806. else
  807. {
  808. const image = v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this));
  809. image.onload = () =>
  810. {
  811. this.v86.cpu.devices.fdc.set_fda(image);
  812. };
  813. await image.load();
  814. }
  815. };
  816. /**
  817. * Eject the floppy drive.
  818. * @export
  819. */
  820. V86.prototype.eject_fda = function()
  821. {
  822. this.v86.cpu.devices.fdc.eject_fda();
  823. };
  824. /**
  825. * Send a sequence of scan codes to the emulated PS2 controller. A list of
  826. * codes can be found at http://stanislavs.org/helppc/make_codes.html.
  827. * Do nothing if there is no keyboard controller.
  828. *
  829. * @param {Array.<number>} codes
  830. * @export
  831. */
  832. V86.prototype.keyboard_send_scancodes = function(codes)
  833. {
  834. for(var i = 0; i < codes.length; i++)
  835. {
  836. this.bus.send("keyboard-code", codes[i]);
  837. }
  838. };
  839. /**
  840. * Send translated keys
  841. * @ignore
  842. * @export
  843. */
  844. V86.prototype.keyboard_send_keys = function(codes)
  845. {
  846. for(var i = 0; i < codes.length; i++)
  847. {
  848. this.keyboard_adapter.simulate_press(codes[i]);
  849. }
  850. };
  851. /**
  852. * Send text
  853. * @ignore
  854. * @export
  855. */
  856. V86.prototype.keyboard_send_text = function(string)
  857. {
  858. for(var i = 0; i < string.length; i++)
  859. {
  860. this.keyboard_adapter.simulate_char(string[i]);
  861. }
  862. };
  863. /**
  864. * Download a screenshot.
  865. *
  866. * @ignore
  867. * @export
  868. */
  869. V86.prototype.screen_make_screenshot = function()
  870. {
  871. if(this.screen_adapter)
  872. {
  873. return this.screen_adapter.make_screenshot();
  874. }
  875. return null;
  876. };
  877. /**
  878. * Set the scaling level of the emulated screen.
  879. *
  880. * @param {number} sx
  881. * @param {number} sy
  882. *
  883. * @ignore
  884. * @export
  885. */
  886. V86.prototype.screen_set_scale = function(sx, sy)
  887. {
  888. if(this.screen_adapter)
  889. {
  890. this.screen_adapter.set_scale(sx, sy);
  891. }
  892. };
  893. /**
  894. * Go fullscreen.
  895. *
  896. * @ignore
  897. * @export
  898. */
  899. V86.prototype.screen_go_fullscreen = function()
  900. {
  901. if(!this.screen_adapter)
  902. {
  903. return;
  904. }
  905. var elem = document.getElementById("screen_container");
  906. if(!elem)
  907. {
  908. return;
  909. }
  910. // bracket notation because otherwise they get renamed by closure compiler
  911. var fn = elem["requestFullScreen"] ||
  912. elem["webkitRequestFullscreen"] ||
  913. elem["mozRequestFullScreen"] ||
  914. elem["msRequestFullScreen"];
  915. if(fn)
  916. {
  917. fn.call(elem);
  918. // This is necessary, because otherwise chromium keyboard doesn't work anymore.
  919. // Might (but doesn't seem to) break something else
  920. var focus_element = document.getElementsByClassName("phone_keyboard")[0];
  921. focus_element && focus_element.focus();
  922. }
  923. try {
  924. navigator.keyboard.lock();
  925. } catch(e) {}
  926. this.lock_mouse();
  927. };
  928. /**
  929. * Lock the mouse cursor: It becomes invisble and is not moved out of the
  930. * browser window.
  931. *
  932. * @ignore
  933. * @export
  934. */
  935. V86.prototype.lock_mouse = function()
  936. {
  937. var elem = document.body;
  938. var fn = elem["requestPointerLock"] ||
  939. elem["mozRequestPointerLock"] ||
  940. elem["webkitRequestPointerLock"];
  941. if(fn)
  942. {
  943. fn.call(elem);
  944. }
  945. };
  946. /**
  947. * Enable or disable sending mouse events to the emulated PS2 controller.
  948. *
  949. * @param {boolean} enabled
  950. */
  951. V86.prototype.mouse_set_status = function(enabled)
  952. {
  953. if(this.mouse_adapter)
  954. {
  955. this.mouse_adapter.emu_enabled = enabled;
  956. }
  957. };
  958. /**
  959. * Enable or disable sending keyboard events to the emulated PS2 controller.
  960. *
  961. * @param {boolean} enabled
  962. * @export
  963. */
  964. V86.prototype.keyboard_set_status = function(enabled)
  965. {
  966. if(this.keyboard_adapter)
  967. {
  968. this.keyboard_adapter.emu_enabled = enabled;
  969. }
  970. };
  971. /**
  972. * Send a string to the first emulated serial terminal.
  973. *
  974. * @param {string} data
  975. * @export
  976. */
  977. V86.prototype.serial0_send = function(data)
  978. {
  979. for(var i = 0; i < data.length; i++)
  980. {
  981. this.bus.send("serial0-input", data.charCodeAt(i));
  982. }
  983. };
  984. /**
  985. * Send bytes to a serial port (to be received by the emulated PC).
  986. *
  987. * @param {Uint8Array} data
  988. * @export
  989. */
  990. V86.prototype.serial_send_bytes = function(serial, data)
  991. {
  992. for(var i = 0; i < data.length; i++)
  993. {
  994. this.bus.send("serial" + serial + "-input", data[i]);
  995. }
  996. };
  997. /**
  998. * Set the modem status of a serial port.
  999. */
  1000. V86.prototype.serial_set_modem_status = function(serial, status)
  1001. {
  1002. this.bus.send("serial" + serial + "-modem-status-input", status);
  1003. };
  1004. /**
  1005. * Set the carrier detect status of a serial port.
  1006. */
  1007. V86.prototype.serial_set_carrier_detect = function(serial, status)
  1008. {
  1009. this.bus.send("serial" + serial + "-carrier-detect-input", status);
  1010. };
  1011. /**
  1012. * Set the ring indicator status of a serial port.
  1013. */
  1014. V86.prototype.serial_set_ring_indicator = function(serial, status)
  1015. {
  1016. this.bus.send("serial" + serial + "-ring-indicator-input", status);
  1017. };
  1018. /**
  1019. * Set the data set ready status of a serial port.
  1020. */
  1021. V86.prototype.serial_set_data_set_ready = function(serial, status)
  1022. {
  1023. this.bus.send("serial" + serial + "-data-set-ready-input", status);
  1024. };
  1025. /**
  1026. * Set the clear to send status of a serial port.
  1027. */
  1028. V86.prototype.serial_set_clear_to_send = function(serial, status)
  1029. {
  1030. this.bus.send("serial" + serial + "-clear-to-send-input", status);
  1031. };
  1032. /**
  1033. * Mount another filesystem to the current filesystem.
  1034. * @param {string} path Path for the mount point
  1035. * @param {string|undefined} baseurl
  1036. * @param {string|undefined} basefs As a JSON string
  1037. * @param {function(Object)=} callback
  1038. * @export
  1039. */
  1040. V86.prototype.mount_fs = async function(path, baseurl, basefs, callback)
  1041. {
  1042. let file_storage = new MemoryFileStorage();
  1043. if(baseurl)
  1044. {
  1045. file_storage = new ServerFileStorageWrapper(file_storage, baseurl);
  1046. }
  1047. const newfs = new FS(file_storage, this.fs9p.qidcounter);
  1048. const mount = () =>
  1049. {
  1050. const idx = this.fs9p.Mount(path, newfs);
  1051. if(!callback)
  1052. {
  1053. return;
  1054. }
  1055. if(idx === -ENOENT)
  1056. {
  1057. callback(new FileNotFoundError());
  1058. }
  1059. else if(idx === -EEXIST)
  1060. {
  1061. callback(new FileExistsError());
  1062. }
  1063. else if(idx < 0)
  1064. {
  1065. dbg_assert(false, "Unexpected error code: " + (-idx));
  1066. callback(new Error("Failed to mount. Error number: " + (-idx)));
  1067. }
  1068. else
  1069. {
  1070. callback(null);
  1071. }
  1072. };
  1073. if(baseurl)
  1074. {
  1075. dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");
  1076. newfs.load_from_json(basefs, () => mount());
  1077. }
  1078. else
  1079. {
  1080. mount();
  1081. }
  1082. };
  1083. /**
  1084. * Write to a file in the 9p filesystem. Nothing happens if no filesystem has
  1085. * been initialized.
  1086. *
  1087. * @param {string} file
  1088. * @param {Uint8Array} data
  1089. * @export
  1090. */
  1091. V86.prototype.create_file = async function(file, data)
  1092. {
  1093. dbg_assert(arguments.length === 2);
  1094. var fs = this.fs9p;
  1095. if(!fs)
  1096. {
  1097. return;
  1098. }
  1099. var parts = file.split("/");
  1100. var filename = parts[parts.length - 1];
  1101. var path_infos = fs.SearchPath(file);
  1102. var parent_id = path_infos.parentid;
  1103. var not_found = filename === "" || parent_id === -1;
  1104. if(!not_found)
  1105. {
  1106. await fs.CreateBinaryFile(filename, parent_id, data);
  1107. }
  1108. else
  1109. {
  1110. return Promise.reject(new FileNotFoundError());
  1111. }
  1112. };
  1113. /**
  1114. * Read a file in the 9p filesystem. Nothing happens if no filesystem has been
  1115. * initialized.
  1116. *
  1117. * @param {string} file
  1118. * @export
  1119. */
  1120. V86.prototype.read_file = async function(file)
  1121. {
  1122. dbg_assert(arguments.length === 1);
  1123. var fs = this.fs9p;
  1124. if(!fs)
  1125. {
  1126. return;
  1127. }
  1128. const result = await fs.read_file(file);
  1129. if(result)
  1130. {
  1131. return result;
  1132. }
  1133. else
  1134. {
  1135. return Promise.reject(new FileNotFoundError());
  1136. }
  1137. };
  1138. V86.prototype.automatically = function(steps)
  1139. {
  1140. const run = (steps) =>
  1141. {
  1142. const step = steps[0];
  1143. if(!step)
  1144. {
  1145. return;
  1146. }
  1147. const remaining_steps = steps.slice(1);
  1148. if(step.sleep)
  1149. {
  1150. setTimeout(() => run(remaining_steps), step.sleep * 1000);
  1151. return;
  1152. }
  1153. if(step.vga_text)
  1154. {
  1155. const screen = this.screen_adapter.get_text_screen();
  1156. for(let line of screen)
  1157. {
  1158. if(line.includes(step.vga_text))
  1159. {
  1160. run(remaining_steps);
  1161. return;
  1162. }
  1163. }
  1164. setTimeout(() => run(steps), 1000);
  1165. return;
  1166. }
  1167. if(step.keyboard_send)
  1168. {
  1169. if(step.keyboard_send instanceof Array)
  1170. {
  1171. this.keyboard_send_scancodes(step.keyboard_send);
  1172. }
  1173. else
  1174. {
  1175. dbg_assert(typeof step.keyboard_send === "string");
  1176. this.keyboard_send_text(step.keyboard_send);
  1177. }
  1178. run(remaining_steps);
  1179. return;
  1180. }
  1181. if(step.call)
  1182. {
  1183. step.call();
  1184. run(remaining_steps);
  1185. return;
  1186. }
  1187. dbg_assert(false, step);
  1188. };
  1189. run(steps);
  1190. };
  1191. /**
  1192. * Reads data from memory at specified offset.
  1193. *
  1194. * @param {number} offset
  1195. * @param {number} length
  1196. * @returns
  1197. */
  1198. V86.prototype.read_memory = function(offset, length)
  1199. {
  1200. return this.v86.cpu.read_blob(offset, length);
  1201. };
  1202. /**
  1203. * Writes data to memory at specified offset.
  1204. *
  1205. * @param {Array.<number>|Uint8Array} blob
  1206. * @param {number} offset
  1207. */
  1208. V86.prototype.write_memory = function(blob, offset)
  1209. {
  1210. this.v86.cpu.write_blob(blob, offset);
  1211. };
  1212. V86.prototype.set_serial_container_xtermjs = function(element)
  1213. {
  1214. this.serial_adapter && this.serial_adapter.destroy && this.serial_adapter.destroy();
  1215. this.serial_adapter = new SerialAdapterXtermJS(element, this.bus);
  1216. this.serial_adapter.show();
  1217. };
  1218. /**
  1219. * @ignore
  1220. * @constructor
  1221. *
  1222. * @param {string=} message
  1223. */
  1224. function FileExistsError(message)
  1225. {
  1226. this.message = message || "File already exists";
  1227. }
  1228. FileExistsError.prototype = Error.prototype;
  1229. /**
  1230. * @ignore
  1231. * @constructor
  1232. *
  1233. * @param {string=} message
  1234. */
  1235. function FileNotFoundError(message)
  1236. {
  1237. this.message = message || "File not found";
  1238. }
  1239. FileNotFoundError.prototype = Error.prototype;
  1240. // Closure Compiler's way of exporting
  1241. if(typeof window !== "undefined")
  1242. {
  1243. window["V86Starter"] = V86;
  1244. window["V86"] = V86;
  1245. }
  1246. else if(typeof module !== "undefined" && typeof module.exports !== "undefined")
  1247. {
  1248. module.exports["V86Starter"] = V86;
  1249. module.exports["V86"] = V86;
  1250. }
  1251. else if(typeof importScripts === "function")
  1252. {
  1253. // web worker
  1254. self["V86Starter"] = V86;
  1255. self["V86"] = V86;
  1256. }