starter.js 38 KB

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