starter.js 36 KB

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