starter.js 38 KB

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