starter.js 36 KB

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