1
0

starter.js 40 KB

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