starter.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  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. * - `initial_state Object` (Normal boot) - An initial state to load, see
  30. * [`restore_state`](#restore_statearraybuffer-state) and below.
  31. *
  32. * - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
  33. * [filesystem.md](filesystem.md).
  34. *
  35. * - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
  36. * that will receive and send data to the emulated serial terminal.
  37. * Alternatively the serial terminal can also be accessed programatically,
  38. * see [serial.html](../examples/serial.html).
  39. *
  40. * - `screen_container HTMLElement` (No screen) - An HTMLElement. This should
  41. * have a certain structure, see [basic.html](../examples/basic.html).
  42. *
  43. * ***
  44. *
  45. * There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
  46. *
  47. * - Pass an object that has a url. Optionally, `async: true` and `size:
  48. * size_in_bytes` can be added to the object, so that sectors of the image
  49. * are loaded on demand instead of being loaded before boot (slower, but
  50. * strongly recommended for big files). In that case, the `Range: bytes=...`
  51. * header must be supported on the server.
  52. *
  53. * ```javascript
  54. * // download file before boot
  55. * options.bios = {
  56. * url: "bios/seabios.bin"
  57. * }
  58. * // download file sectors as requested, size is required
  59. * options.hda = {
  60. * url: "disk/linux.iso",
  61. * async: true,
  62. * size: 16 * 1024 * 1024
  63. * }
  64. * ```
  65. *
  66. * - Pass an `ArrayBuffer` or `File` object as `buffer` property.
  67. *
  68. * ```javascript
  69. * // use <input type=file>
  70. * options.bios = {
  71. * buffer: document.all.hd_image.files[0]
  72. * }
  73. * // start with empty hard drive
  74. * options.hda = {
  75. * buffer: new ArrayBuffer(16 * 1024 * 1024)
  76. * }
  77. * ```
  78. *
  79. * ***
  80. *
  81. * @param {Object} options Options to initialize the emulator with.
  82. * @constructor
  83. */
  84. function V86Starter(options)
  85. {
  86. //var worker = new Worker("src/browser/worker.js");
  87. //var adapter_bus = this.bus = WorkerBus.init(worker);
  88. this.cpu_is_running = false;
  89. var bus = Bus.create();
  90. var adapter_bus = this.bus = bus[0];
  91. this.emulator_bus = bus[1];
  92. var emulator;
  93. var cpu;
  94. var mem;
  95. var mem8;
  96. const coverage_logger = new CoverageLogger();
  97. if(coverage_logger.ENABLED)
  98. {
  99. this.bus.register("emulator-stopped", function()
  100. {
  101. coverage_logger.dump_to_files();
  102. }, this);
  103. }
  104. var wasm_shared_funcs = {
  105. "___assert_fail": (condition, file, line, fun) => {
  106. const memory = mem8;
  107. function read_string(memory, offset)
  108. {
  109. memory = memory.subarray(offset);
  110. const zero = memory.indexOf(0);
  111. if(zero !== -1)
  112. {
  113. memory = memory.subarray(0, zero);
  114. }
  115. return String.fromCharCode.apply(String, memory);
  116. }
  117. console.error("Assertion Failed: '%s' at %s:%d in %s",
  118. read_string(memory, condition),
  119. read_string(memory, file),
  120. line,
  121. read_string(memory, fun));
  122. dbg_assert(false);
  123. },
  124. "_throw_cpu_exception": () => {
  125. throw MAGIC_CPU_EXCEPTION;
  126. },
  127. "_cpu_exception_hook": (n) => {
  128. return this["cpu_exception_hook"] && this["cpu_exception_hook"](n);
  129. },
  130. "_hlt_op": function() { return cpu.hlt_op(); },
  131. "abort": function() { dbg_assert(false); },
  132. "__dbg_trace": function() { return dbg_trace(); },
  133. "_logop": function(eip, op) { return cpu.debug.logop(eip, op); },
  134. "_todo": function() { return cpu.todo.apply(cpu, arguments); },
  135. "_undefined_instruction": function() { return cpu.undefined_instruction.apply(cpu, arguments); },
  136. "_unimplemented_sse": function() { return cpu.unimplemented_sse(); },
  137. "_microtick": v86.microtick,
  138. "_get_rand_int": function() { return v86util.get_rand_int(); },
  139. "_has_rand_int": function() { return v86util.has_rand_int(); },
  140. "_printf": function(format_string_offset, stack_top) {
  141. dbg_assert(arguments.length === 2);
  142. dbg_log_wasm(mem, format_string_offset, stack_top);
  143. },
  144. "_memcpy_large": function(dest, source, length) {
  145. mem8.set(mem8.subarray(source, source + length), dest);
  146. return dest;
  147. },
  148. "_memcpy": function(dest, source, length) {
  149. mem8.set(mem8.subarray(source, source + length), dest);
  150. return dest;
  151. },
  152. "_call_interrupt_vector": function(interrupt_nr, is_software_int, has_error_code, error_code) {
  153. cpu.call_interrupt_vector(interrupt_nr, is_software_int, !!has_error_code, error_code);
  154. },
  155. "_far_jump": function(eip, selector, is_call) { return cpu.far_jump(eip, selector, !!is_call); },
  156. "_far_return": function(eip, selector, stack_adjust) { return cpu.far_return(eip, selector, stack_adjust); },
  157. "_switch_seg": function(reg, selector) { cpu.switch_seg(reg, selector); },
  158. "_iret16": function() { return cpu.iret16(); },
  159. "_iret32": function() { return cpu.iret32(); },
  160. "_handle_irqs": function() { return cpu.handle_irqs(); },
  161. "_io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
  162. "_io_port_read16": function(addr) { return cpu.io.port_read16(addr); },
  163. "_io_port_read32": function(addr) { return cpu.io.port_read32(addr); },
  164. "_io_port_write8": function(addr, value) { cpu.io.port_write8(addr, value); },
  165. "_io_port_write16": function(addr, value) { cpu.io.port_write16(addr, value); },
  166. "_io_port_write32": function(addr, value) { cpu.io.port_write32(addr, value); },
  167. "_mmap_read8": function(addr) { return cpu.mmap_read8(addr); },
  168. "_mmap_read16": function(addr) { return cpu.mmap_read16(addr); },
  169. "_mmap_read32": function(addr) { return cpu.mmap_read32(addr); },
  170. "_mmap_write8": function(addr, value) { return cpu.mmap_write8(addr, value); },
  171. "_mmap_write16": function(addr, value) { return cpu.mmap_write16(addr, value); },
  172. "_mmap_write32": function(addr, value) { return cpu.mmap_write32(addr, value); },
  173. "_mmap_write128": function(addr, value0, value1, value2, value3) { return cpu.mmap_write128(addr, value0, value1, value2, value3); },
  174. "_int_log2": function(val) { return v86util.int_log2(val); },
  175. "_popa16": function() { return cpu.popa16.apply(cpu, arguments); },
  176. "_popa32": function() { return cpu.popa32.apply(cpu, arguments); },
  177. "_arpl": function() { return cpu.arpl.apply(cpu, arguments); },
  178. "_bswap": function() { return cpu.bswap.apply(cpu, arguments); },
  179. "_lar": function() { return cpu.lar.apply(cpu, arguments); },
  180. "_lsl": function() { return cpu.lsl.apply(cpu, arguments); },
  181. "_verw": function() { return cpu.verw.apply(cpu, arguments); },
  182. "_verr": function() { return cpu.verr.apply(cpu, arguments); },
  183. "_cpl_changed": function() { return cpu.cpl_changed.apply(cpu, arguments); },
  184. "_set_cr0": function() { return cpu.set_cr0.apply(cpu, arguments); },
  185. "_update_cs_size": function() { return cpu.update_cs_size.apply(cpu, arguments); },
  186. "_cpuid": function() { return cpu.cpuid.apply(cpu, arguments); },
  187. "_load_ldt": function() { return cpu.load_ldt.apply(cpu, arguments); },
  188. "_load_tr": function() { return cpu.load_tr.apply(cpu, arguments); },
  189. "_lss16": function() { return cpu.lss16.apply(cpu, arguments); },
  190. "_lss32": function() { return cpu.lss32.apply(cpu, arguments); },
  191. "_enter16": function() { return cpu.enter16.apply(cpu, arguments); },
  192. "_enter32": function() { return cpu.enter32.apply(cpu, arguments); },
  193. "_test_privileges_for_io": function() { return cpu.test_privileges_for_io.apply(cpu, arguments); },
  194. "_convert_f64_to_i32": function(f) {
  195. // implemented here due to emscripten bug
  196. if(!(f <= 0x7FFFFFFF && f >= -0x80000000))
  197. {
  198. f = 0x80000000 | 0;
  199. }
  200. return f | 0;
  201. },
  202. "_get_time": Date.now,
  203. "_coverage_log": (fn_name_offset, num_blocks, visited_block) => {
  204. coverage_logger.log(fn_name_offset, num_blocks, visited_block);
  205. },
  206. // see https://github.com/kripken/emscripten/blob/incoming/src/library.js
  207. "_atan2": Math.atan2,
  208. "_sin": Math.sin,
  209. "_cos": Math.cos,
  210. "_tan": Math.tan,
  211. "_trunc": Math.trunc,
  212. "_fmod": (x, y) => x % y,
  213. "_llvm_exp2_f64": (x) => Math.pow(2, x),
  214. "_log": Math.log,
  215. "_round": Math.round,
  216. "_ldexp": function(x, exp) {
  217. return x * Math.pow(2, exp);
  218. },
  219. "_llvm_round_f64": function(d) {
  220. d = +d;
  221. return d >= +0 ? +Math.floor(d + 0.5) : +Math.ceil(d - 0.5);
  222. },
  223. "_llvm_trunc_f64": Math.trunc,
  224. };
  225. const wasm_globals = {
  226. "Infinity": Infinity,
  227. "NaN": NaN,
  228. };
  229. const v86oxide_mem = new WebAssembly.Memory({ "initial": 250 });
  230. const v86oxide_externs = {
  231. "memory": v86oxide_mem,
  232. "log_from_wasm": function(offset, len) {
  233. const str = v86util.read_sized_string_from_mem(v86oxide_mem, offset, len);
  234. dbg_log(str, LOG_CPU);
  235. },
  236. "abort": function() {
  237. dbg_assert(false);
  238. },
  239. "read8": addr => cpu.read8(addr),
  240. "read16": addr => cpu.read16(addr),
  241. "read32": addr => cpu.read32s(addr),
  242. "tlb_set_has_code": (page, has_code) => cpu.wm.exports["_tlb_set_has_code"](page, has_code),
  243. "check_tlb_invariants": () => cpu.wm.exports["_check_tlb_invariants"](),
  244. "codegen_finalize": (wasm_table_index, start, end, first_opcode, state_flags) => cpu.codegen_finalize(wasm_table_index, start, end, first_opcode, state_flags),
  245. "profiler_stat_increment": (name) => cpu.wm.exports["_profiler_stat_increment"](name),
  246. };
  247. let wasm_file = DEBUG ? "v86-debug.wasm" : "v86.wasm";
  248. let v86oxide_bin = DEBUG ? "v86oxide-debug.wasm" : "v86oxide.wasm";
  249. if(typeof window === "undefined" && typeof __dirname === "string")
  250. {
  251. wasm_file = __dirname + "/" + wasm_file;
  252. v86oxide_bin = __dirname + "/" + v86oxide_bin;
  253. }
  254. else
  255. {
  256. wasm_file = "build/" + wasm_file;
  257. v86oxide_bin = "build/" + v86oxide_bin;
  258. }
  259. const v86oxide_exports = [
  260. // For C:
  261. "jit_get_entry_pending",
  262. "jit_get_entry_address",
  263. "jit_get_entry_length",
  264. "jit_unused_cache_stat",
  265. "jit_dirty_cache_single",
  266. "jit_dirty_cache_small",
  267. "jit_page_has_code",
  268. "jit_increase_hotness_and_maybe_compile",
  269. "jit_find_cache_entry",
  270. // For JS:
  271. "jit_empty_cache",
  272. "codegen_finalize_finished",
  273. "rust_setup",
  274. "jit_dirty_cache",
  275. ];
  276. v86util.minimal_load_wasm(v86oxide_bin, { "env": v86oxide_externs }, (v86oxide) => {
  277. for(const fn_name of v86oxide_exports)
  278. {
  279. dbg_assert(typeof v86oxide.exports[fn_name] === "function", `Function ${fn_name} not found in v86oxide exports`);
  280. wasm_shared_funcs[`_${fn_name}`] = v86oxide.exports[fn_name];
  281. }
  282. v86oxide.exports["rust_setup"]();
  283. //XXX: fix indentation break
  284. v86util.load_wasm(
  285. wasm_file,
  286. { "env": wasm_shared_funcs, "global" : wasm_globals },
  287. options["memory_size"] + GUEST_MEMORY_START,
  288. WASM_TABLE_SIZE,
  289. wm => {
  290. mem = wm.memory.buffer;
  291. mem8 = new Uint8Array(mem);
  292. wm.instance.exports["__post_instantiate"]();
  293. coverage_logger.init(wm);
  294. emulator = this.v86 = new v86(this.emulator_bus, wm, v86oxide, coverage_logger);
  295. cpu = emulator.cpu;
  296. // XXX: Leaving unindented to minimize diff; still a part of the cb to load_wasm!
  297. this.bus.register("emulator-stopped", function()
  298. {
  299. this.cpu_is_running = false;
  300. }, this);
  301. this.bus.register("emulator-started", function()
  302. {
  303. this.cpu_is_running = true;
  304. }, this);
  305. var settings = {};
  306. this.disk_images = {
  307. "fda": undefined,
  308. "fdb": undefined,
  309. "hda": undefined,
  310. "hdb": undefined,
  311. "cdrom": undefined,
  312. };
  313. settings.load_devices = true;
  314. settings.log_level = options["log_level"];
  315. settings.memory_size = options["memory_size"] || 64 * 1024 * 1024;
  316. settings.vga_memory_size = options["vga_memory_size"] || 8 * 1024 * 1024;
  317. settings.boot_order = options["boot_order"] || 0x213;
  318. settings.fda = undefined;
  319. settings.fdb = undefined;
  320. if(options["network_relay_url"])
  321. {
  322. this.network_adapter = new NetworkAdapter(options["network_relay_url"], adapter_bus);
  323. settings.enable_ne2k = true;
  324. }
  325. if(!options["disable_keyboard"])
  326. {
  327. this.keyboard_adapter = new KeyboardAdapter(adapter_bus);
  328. }
  329. if(!options["disable_mouse"])
  330. {
  331. this.mouse_adapter = new MouseAdapter(adapter_bus, options["screen_container"]);
  332. }
  333. if(options["screen_container"])
  334. {
  335. this.screen_adapter = new ScreenAdapter(options["screen_container"], adapter_bus);
  336. }
  337. else if(options["screen_dummy"])
  338. {
  339. this.screen_adapter = new DummyScreenAdapter(adapter_bus);
  340. }
  341. if(options["serial_container"])
  342. {
  343. this.serial_adapter = new SerialAdapter(options["serial_container"], adapter_bus);
  344. }
  345. // ugly, but required for closure compiler compilation
  346. function put_on_settings(name, buffer)
  347. {
  348. switch(name)
  349. {
  350. case "hda":
  351. settings.hda = this.disk_images["hda"] = buffer;
  352. break;
  353. case "hdb":
  354. settings.hdb = this.disk_images["hdb"] = buffer;
  355. break;
  356. case "cdrom":
  357. settings.cdrom = this.disk_images["cdrom"] = buffer;
  358. break;
  359. case "fda":
  360. settings.fda = this.disk_images["fda"] = buffer;
  361. break;
  362. case "fdb":
  363. settings.fdb = this.disk_images["fdb"] = buffer;
  364. break;
  365. case "multiboot":
  366. settings.multiboot = this.disk_images["multiboot"] = buffer;
  367. break;
  368. case "bios":
  369. settings.bios = buffer.buffer;
  370. break;
  371. case "vga_bios":
  372. settings.vga_bios = buffer.buffer;
  373. break;
  374. case "initial_state":
  375. settings.initial_state = buffer.buffer;
  376. break;
  377. case "fs9p_json":
  378. settings.fs9p_json = buffer.buffer;
  379. break;
  380. default:
  381. dbg_assert(false, name);
  382. }
  383. }
  384. var files_to_load = [];
  385. function add_file(name, file)
  386. {
  387. if(!file)
  388. {
  389. return;
  390. }
  391. if(file["get"] && file["set"] && file["load"])
  392. {
  393. files_to_load.push({
  394. name: name,
  395. loadable: file,
  396. });
  397. return;
  398. }
  399. // Anything coming from the outside world needs to be quoted for
  400. // Closure Compiler compilation
  401. file = {
  402. buffer: file["buffer"],
  403. async: file["async"],
  404. url: file["url"],
  405. size: file["size"],
  406. };
  407. if(name === "bios" || name === "vga_bios" ||
  408. name === "initial_state" || name === "multiboot")
  409. {
  410. // Ignore async for these because they must be availabe before boot.
  411. // This should make result.buffer available after the object is loaded
  412. file.async = false;
  413. }
  414. if(file.buffer instanceof ArrayBuffer)
  415. {
  416. var buffer = new SyncBuffer(file.buffer);
  417. files_to_load.push({
  418. name: name,
  419. loadable: buffer,
  420. });
  421. }
  422. else if(typeof File !== "undefined" && file.buffer instanceof File)
  423. {
  424. // SyncFileBuffer:
  425. // - loads the whole disk image into memory, impossible for large files (more than 1GB)
  426. // - can later serve get/set operations fast and synchronously
  427. // - takes some time for first load, neglectable for small files (up to 100Mb)
  428. //
  429. // AsyncFileBuffer:
  430. // - loads slices of the file asynchronously as requested
  431. // - slower get/set
  432. // Heuristics: If file is larger than or equal to 256M, use AsyncFileBuffer
  433. if(file.async === undefined)
  434. {
  435. file.async = file.buffer.size >= 256 * 1024 * 1024;
  436. }
  437. if(file.async)
  438. {
  439. var buffer = new v86util.AsyncFileBuffer(file.buffer);
  440. }
  441. else
  442. {
  443. var buffer = new v86util.SyncFileBuffer(file.buffer);
  444. }
  445. files_to_load.push({
  446. name: name,
  447. loadable: buffer,
  448. });
  449. }
  450. else if(file.url)
  451. {
  452. if(file.async)
  453. {
  454. var buffer = new v86util.AsyncXHRBuffer(file.url, file.size);
  455. files_to_load.push({
  456. name: name,
  457. loadable: buffer,
  458. });
  459. }
  460. else
  461. {
  462. files_to_load.push({
  463. name: name,
  464. url: file.url,
  465. size: file.size,
  466. });
  467. }
  468. }
  469. else
  470. {
  471. dbg_log("Ignored file: url=" + file.url + " buffer=" + file.buffer);
  472. }
  473. }
  474. if(options["state"])
  475. {
  476. console.warn("Warning: Unknown option 'state'. Did you mean 'initial_state'?");
  477. }
  478. var image_names = [
  479. "bios", "vga_bios",
  480. "cdrom", "hda", "hdb", "fda", "fdb",
  481. "initial_state", "multiboot",
  482. ];
  483. for(var i = 0; i < image_names.length; i++)
  484. {
  485. add_file(image_names[i], options[image_names[i]]);
  486. }
  487. if(options["filesystem"])
  488. {
  489. var fs_url = options["filesystem"]["basefs"];
  490. var base_url = options["filesystem"]["baseurl"];
  491. this.fs9p = new FS(base_url);
  492. settings.fs9p = this.fs9p;
  493. if(fs_url)
  494. {
  495. console.assert(base_url, "Filesystem: baseurl must be specified");
  496. var size;
  497. if(typeof fs_url === "object")
  498. {
  499. size = fs_url["size"];
  500. fs_url = fs_url["url"];
  501. }
  502. dbg_assert(typeof fs_url === "string");
  503. files_to_load.push({
  504. name: "fs9p_json",
  505. url: fs_url,
  506. size: size,
  507. as_text: true,
  508. });
  509. }
  510. }
  511. var starter = this;
  512. var total = files_to_load.length;
  513. var cont = function(index)
  514. {
  515. if(index === total)
  516. {
  517. setTimeout(done.bind(this), 0);
  518. return;
  519. }
  520. var f = files_to_load[index];
  521. if(f.loadable)
  522. {
  523. f.loadable.onload = function(e)
  524. {
  525. put_on_settings.call(this, f.name, f.loadable);
  526. cont(index + 1);
  527. }.bind(this);
  528. f.loadable.load();
  529. }
  530. else
  531. {
  532. v86util.load_file(f.url, {
  533. done: function(result)
  534. {
  535. put_on_settings.call(this, f.name, new SyncBuffer(result));
  536. cont(index + 1);
  537. }.bind(this),
  538. progress: function progress(e)
  539. {
  540. if(e.target.status === 200)
  541. {
  542. starter.emulator_bus.send("download-progress", {
  543. file_index: index,
  544. file_count: total,
  545. file_name: f.url,
  546. lengthComputable: e.lengthComputable,
  547. total: e.total || f.size,
  548. loaded: e.loaded,
  549. });
  550. }
  551. else
  552. {
  553. starter.emulator_bus.send("download-error", {
  554. file_index: index,
  555. file_count: total,
  556. file_name: f.url,
  557. request: e.target,
  558. });
  559. }
  560. },
  561. as_text: f.as_text,
  562. });
  563. }
  564. }.bind(this);
  565. cont(0);
  566. function done()
  567. {
  568. //if(settings.initial_state)
  569. //{
  570. // // avoid large allocation now, memory will be restored later anyway
  571. // settings.memory_size = 0;
  572. //}
  573. this.bus.send("cpu-init", settings);
  574. setTimeout(function()
  575. {
  576. if(settings.fs9p && settings.fs9p_json)
  577. {
  578. settings.fs9p.OnJSONLoaded(settings.fs9p_json);
  579. }
  580. setTimeout(function()
  581. {
  582. if(settings.initial_state)
  583. {
  584. emulator.restore_state(settings.initial_state);
  585. // The GC can't free settings, since it is referenced from
  586. // several closures. This isn't needed anymore, so we delete it
  587. // here
  588. settings.initial_state = undefined;
  589. }
  590. if(options["autostart"])
  591. {
  592. this.bus.send("cpu-run");
  593. }
  594. this.emulator_bus.send("emulator-loaded");
  595. }.bind(this), 0);
  596. }.bind(this), 0);
  597. }
  598. });
  599. });
  600. }
  601. /**
  602. * Start emulation. Do nothing if emulator is running already. Can be
  603. * asynchronous.
  604. * @export
  605. */
  606. V86Starter.prototype.run = function()
  607. {
  608. this.bus.send("cpu-run");
  609. };
  610. /**
  611. * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
  612. * @export
  613. */
  614. V86Starter.prototype.stop = function()
  615. {
  616. this.bus.send("cpu-stop");
  617. };
  618. /**
  619. * @ignore
  620. * @export
  621. */
  622. V86Starter.prototype.destroy = function()
  623. {
  624. this.keyboard_adapter.destroy();
  625. };
  626. /**
  627. * Restart (force a reboot).
  628. * @export
  629. */
  630. V86Starter.prototype.restart = function()
  631. {
  632. this.bus.send("cpu-restart");
  633. };
  634. /**
  635. * Add an event listener (the emulator is an event emitter). A list of events
  636. * can be found at [events.md](events.md).
  637. *
  638. * The callback function gets a single argument which depends on the event.
  639. *
  640. * @param {string} event Name of the event.
  641. * @param {function(*)} listener The callback function.
  642. * @export
  643. */
  644. V86Starter.prototype.add_listener = function(event, listener)
  645. {
  646. this.bus.register(event, listener, this);
  647. };
  648. /**
  649. * Remove an event listener.
  650. *
  651. * @param {string} event
  652. * @param {function(*)} listener
  653. * @export
  654. */
  655. V86Starter.prototype.remove_listener = function(event, listener)
  656. {
  657. this.bus.unregister(event, listener);
  658. };
  659. /**
  660. * Restore the emulator state from the given state, which must be an
  661. * ArrayBuffer returned by
  662. * [`save_state`](#save_statefunctionobject-arraybuffer-callback).
  663. *
  664. * Note that the state can only be restored correctly if this constructor has
  665. * been created with the same options as the original instance (e.g., same disk
  666. * images, memory size, etc.).
  667. *
  668. * Different versions of the emulator might use a different format for the
  669. * state buffer.
  670. *
  671. * @param {ArrayBuffer} state
  672. * @export
  673. */
  674. V86Starter.prototype.restore_state = function(state)
  675. {
  676. this.v86.restore_state(state);
  677. };
  678. /**
  679. * Asynchronously save the current state of the emulator. The first argument to
  680. * the callback is an Error object if something went wrong and is null
  681. * otherwise.
  682. *
  683. * @param {function(Object, ArrayBuffer)} callback
  684. * @export
  685. */
  686. V86Starter.prototype.save_state = function(callback)
  687. {
  688. // Might become asynchronous at some point
  689. setTimeout(function()
  690. {
  691. try
  692. {
  693. callback(null, this.v86.save_state());
  694. }
  695. catch(e)
  696. {
  697. callback(e, null);
  698. }
  699. }.bind(this), 0);
  700. };
  701. /**
  702. * Return an object with several statistics. Return value looks similar to
  703. * (but can be subject to change in future versions or different
  704. * configurations, so use defensively):
  705. *
  706. * ```javascript
  707. * {
  708. * "cpu": {
  709. * "instruction_counter": 2821610069
  710. * },
  711. * "hda": {
  712. * "sectors_read": 95240,
  713. * "sectors_written": 952,
  714. * "bytes_read": 48762880,
  715. * "bytes_written": 487424,
  716. * "loading": false
  717. * },
  718. * "cdrom": {
  719. * "sectors_read": 0,
  720. * "sectors_written": 0,
  721. * "bytes_read": 0,
  722. * "bytes_written": 0,
  723. * "loading": false
  724. * },
  725. * "mouse": {
  726. * "enabled": true
  727. * },
  728. * "vga": {
  729. * "is_graphical": true,
  730. * "res_x": 800,
  731. * "res_y": 600,
  732. * "bpp": 32
  733. * }
  734. * }
  735. * ```
  736. *
  737. * @deprecated
  738. * @return {Object}
  739. * @export
  740. */
  741. V86Starter.prototype.get_statistics = function()
  742. {
  743. console.warn("V86Starter.prototype.get_statistics is deprecated. Use events instead.");
  744. var stats = {
  745. cpu: {
  746. instruction_counter: this.get_instruction_counter(),
  747. },
  748. };
  749. if(!this.v86)
  750. {
  751. return stats;
  752. }
  753. var devices = this.v86.cpu.devices;
  754. if(devices.hda)
  755. {
  756. stats.hda = devices.hda.stats;
  757. }
  758. if(devices.cdrom)
  759. {
  760. stats.cdrom = devices.cdrom.stats;
  761. }
  762. if(devices.ps2)
  763. {
  764. stats["mouse"] = {
  765. "enabled": devices.ps2.use_mouse,
  766. };
  767. }
  768. if(devices.vga)
  769. {
  770. stats["vga"] = {
  771. "is_graphical": devices.vga.stats.is_graphical,
  772. };
  773. }
  774. return stats;
  775. };
  776. /**
  777. * @return {number}
  778. * @ignore
  779. * @export
  780. */
  781. V86Starter.prototype.get_instruction_counter = function()
  782. {
  783. if(this.v86)
  784. {
  785. return this.v86.cpu.timestamp_counter[0];
  786. }
  787. else
  788. {
  789. // TODO: Should be handled using events
  790. return 0;
  791. }
  792. };
  793. /**
  794. * @return {boolean}
  795. * @export
  796. */
  797. V86Starter.prototype.is_running = function()
  798. {
  799. return this.cpu_is_running;
  800. };
  801. /**
  802. * Send a sequence of scan codes to the emulated PS2 controller. A list of
  803. * codes can be found at http://stanislavs.org/helppc/make_codes.html.
  804. * Do nothing if there is no keyboard controller.
  805. *
  806. * @param {Array.<number>} codes
  807. * @export
  808. */
  809. V86Starter.prototype.keyboard_send_scancodes = function(codes)
  810. {
  811. for(var i = 0; i < codes.length; i++)
  812. {
  813. this.bus.send("keyboard-code", codes[i]);
  814. }
  815. };
  816. /**
  817. * Send translated keys
  818. * @ignore
  819. * @export
  820. */
  821. V86Starter.prototype.keyboard_send_keys = function(codes)
  822. {
  823. for(var i = 0; i < codes.length; i++)
  824. {
  825. this.keyboard_adapter.simulate_press(codes[i]);
  826. }
  827. };
  828. /**
  829. * Send text
  830. * @ignore
  831. * @export
  832. */
  833. V86Starter.prototype.keyboard_send_text = function(string)
  834. {
  835. for(var i = 0; i < string.length; i++)
  836. {
  837. this.keyboard_adapter.simulate_char(string[i]);
  838. }
  839. };
  840. /**
  841. * Download a screenshot.
  842. *
  843. * @ignore
  844. * @export
  845. */
  846. V86Starter.prototype.screen_make_screenshot = function()
  847. {
  848. if(this.screen_adapter)
  849. {
  850. this.screen_adapter.make_screenshot();
  851. }
  852. };
  853. /**
  854. * Set the scaling level of the emulated screen.
  855. *
  856. * @param {number} sx
  857. * @param {number} sy
  858. *
  859. * @ignore
  860. * @export
  861. */
  862. V86Starter.prototype.screen_set_scale = function(sx, sy)
  863. {
  864. if(this.screen_adapter)
  865. {
  866. this.screen_adapter.set_scale(sx, sy);
  867. }
  868. };
  869. /**
  870. * Go fullscreen.
  871. *
  872. * @ignore
  873. * @export
  874. */
  875. V86Starter.prototype.screen_go_fullscreen = function()
  876. {
  877. if(!this.screen_adapter)
  878. {
  879. return;
  880. }
  881. var elem = document.getElementById("screen_container");
  882. if(!elem)
  883. {
  884. return;
  885. }
  886. // bracket notation because otherwise they get renamed by closure compiler
  887. var fn = elem["requestFullScreen"] ||
  888. elem["webkitRequestFullscreen"] ||
  889. elem["mozRequestFullScreen"] ||
  890. elem["msRequestFullScreen"];
  891. if(fn)
  892. {
  893. fn.call(elem);
  894. // This is necessary, because otherwise chromium keyboard doesn't work anymore.
  895. // Might (but doesn't seem to) break something else
  896. var focus_element = document.getElementsByClassName("phone_keyboard")[0];
  897. focus_element && focus_element.focus();
  898. }
  899. //this.lock_mouse(elem);
  900. this.lock_mouse();
  901. };
  902. /**
  903. * Lock the mouse cursor: It becomes invisble and is not moved out of the
  904. * browser window.
  905. *
  906. * @ignore
  907. * @export
  908. */
  909. V86Starter.prototype.lock_mouse = function()
  910. {
  911. var elem = document.body;
  912. var fn = elem["requestPointerLock"] ||
  913. elem["mozRequestPointerLock"] ||
  914. elem["webkitRequestPointerLock"];
  915. if(fn)
  916. {
  917. fn.call(elem);
  918. }
  919. };
  920. /**
  921. * Enable or disable sending mouse events to the emulated PS2 controller.
  922. *
  923. * @param {boolean} enabled
  924. */
  925. V86Starter.prototype.mouse_set_status = function(enabled)
  926. {
  927. if(this.mouse_adapter)
  928. {
  929. this.mouse_adapter.emu_enabled = enabled;
  930. }
  931. };
  932. /**
  933. * Enable or disable sending keyboard events to the emulated PS2 controller.
  934. *
  935. * @param {boolean} enabled
  936. * @export
  937. */
  938. V86Starter.prototype.keyboard_set_status = function(enabled)
  939. {
  940. if(this.keyboard_adapter)
  941. {
  942. this.keyboard_adapter.emu_enabled = enabled;
  943. }
  944. };
  945. /**
  946. * Send a string to the first emulated serial terminal.
  947. *
  948. * @param {string} data
  949. * @export
  950. */
  951. V86Starter.prototype.serial0_send = function(data)
  952. {
  953. for(var i = 0; i < data.length; i++)
  954. {
  955. this.bus.send("serial0-input", data.charCodeAt(i));
  956. }
  957. };
  958. /**
  959. * Write to a file in the 9p filesystem. Nothing happens if no filesystem has
  960. * been initialized. First argument to the callback is an error object if
  961. * something went wrong and null otherwise.
  962. *
  963. * @param {string} file
  964. * @param {Uint8Array} data
  965. * @param {function(Object)=} callback
  966. * @export
  967. */
  968. V86Starter.prototype.create_file = function(file, data, callback)
  969. {
  970. var fs = this.fs9p;
  971. if(!fs)
  972. {
  973. return;
  974. }
  975. var parts = file.split("/");
  976. var filename = parts[parts.length - 1];
  977. var path_infos = fs.SearchPath(file);
  978. var parent_id = path_infos.parentid;
  979. var not_found = filename === "" || parent_id === -1;
  980. if(!not_found)
  981. {
  982. fs.CreateBinaryFile(filename, parent_id, data);
  983. }
  984. if(callback)
  985. {
  986. setTimeout(function()
  987. {
  988. if(not_found)
  989. {
  990. callback(new FileNotFoundError());
  991. }
  992. else
  993. {
  994. callback(null);
  995. }
  996. }, 0);
  997. }
  998. };
  999. /**
  1000. * Read a file in the 9p filesystem. Nothing happens if no filesystem has been
  1001. * initialized.
  1002. *
  1003. * @param {string} file
  1004. * @param {function(Object, Uint8Array)} callback
  1005. * @export
  1006. */
  1007. V86Starter.prototype.read_file = function(file, callback)
  1008. {
  1009. var fs = this.fs9p;
  1010. if(!fs)
  1011. {
  1012. return;
  1013. }
  1014. var path_infos = fs.SearchPath(file);
  1015. var id = path_infos.id;
  1016. if(id === -1)
  1017. {
  1018. callback(new FileNotFoundError(), null);
  1019. }
  1020. else
  1021. {
  1022. fs.OpenInode(id, undefined);
  1023. fs.AddEvent(
  1024. id,
  1025. function()
  1026. {
  1027. var data = fs.inodedata[id];
  1028. if(data)
  1029. {
  1030. callback(null, data.subarray(0, fs.inodes[id].size));
  1031. }
  1032. else
  1033. {
  1034. callback(new FileNotFoundError(), null);
  1035. }
  1036. }
  1037. );
  1038. }
  1039. };
  1040. /**
  1041. * @ignore
  1042. * @constructor
  1043. *
  1044. * @param {string=} message
  1045. */
  1046. function FileNotFoundError(message)
  1047. {
  1048. this.message = message || "File not found";
  1049. }
  1050. FileNotFoundError.prototype = Error.prototype;
  1051. // Closure Compiler's way of exporting
  1052. if(typeof window !== "undefined")
  1053. {
  1054. window["V86Starter"] = V86Starter;
  1055. window["V86"] = V86Starter;
  1056. }
  1057. else if(typeof module !== "undefined" && typeof module.exports !== "undefined")
  1058. {
  1059. module.exports["V86Starter"] = V86Starter;
  1060. module.exports["V86"] = V86Starter;
  1061. }
  1062. else if(typeof importScripts === "function")
  1063. {
  1064. // web worker
  1065. self["V86Starter"] = V86Starter;
  1066. self["V86"] = V86Starter;
  1067. }