blob: a752ffe89ec511c68cc390d245ec2b6802aebfdd [file] [log] [blame] [raw]
"use strict";
(function()
{
// based on https://github.com/Raynos/after
// All the flow control you'll ever need
function after(count, callback)
{
proxy.count = count;
var result = {};
return (count === 0) ? callback() : proxy;
function proxy(data) {
if(proxy.count <= 0)
{
throw new Error("after called too many times");
}
proxy.count--;
if(data)
{
var keys = Object.keys(data);
for(var i = 0; i < keys.length; i++)
{
result[keys[i]] = data[keys[i]];
}
}
if(proxy.count === 0)
{
callback(result);
}
}
}
function dump_file(ab, name)
{
var blob = new Blob([ab]);
var a = document.createElement("a");
a["download"] = name;
a.href = window.URL.createObjectURL(blob),
a.dataset["downloadurl"] = ["application/octet-stream", a["download"], a.href].join(":");
if(document.createEvent)
{
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(ev);
}
else
{
a.click();
}
}
/**
* @return {Object.<string, string>}
*/
function get_query_arguments()
{
var query = location.search.substr(1).split("&");
var parameters = {};
for(var i = 0; i < query.length; i++)
{
var param = query[i].split("=");
parameters[param[0]] = decodeURIComponent(param[1]);
}
return parameters;
}
function set_title(text)
{
document.title = text + " - Virtual x86" + (DEBUG ? " - debug" : "");
}
function time2str(time)
{
if(time < 60)
{
return time + "s";
}
else if(time < 3600)
{
return (time / 60 | 0) + "m " + String.pad0(time % 60, 2) + "s";
}
else
{
return (time / 3600 | 0) + "h " +
String.pad0((time / 60 | 0) % 60, 2) + "m " +
String.pad0(time % 60, 2) + "s";
}
}
function lock_mouse(elem)
{
var fn = elem["requestPointerLock"] ||
elem["mozRequestPointerLock"] ||
elem["webkitRequestPointerLock"];
if(fn)
{
fn.call(elem);
}
}
function chr_repeat(chr, count)
{
var result = "";
while(count-- > 0)
{
result += chr;
}
return result;
}
function show_progress(info, e)
{
var el = $("loading");
el.style.display = "block";
if(e.lengthComputable || (info.total && typeof e.loaded === "number"))
{
var per100 = e.loaded / (e.total || info.total) * 100 | 0;
per100 = Math.min(100, Math.max(0, per100));
el.textContent = info.msg + " " + per100 + "% [" +
chr_repeat("#", per100 >> 1) +
chr_repeat(" ", 50 - (per100 >> 1)) + "]";
}
else
{
if(!info.ticks)
info.ticks = 0;
el.textContent = info.msg + " " + chr_repeat(".", info.ticks++ % 50);
}
}
function $(id)
{
var el = document.getElementById(id);
if(!el)
{
console.log("Element with id `" + id + "` not found");
}
return el;
}
function onload()
{
if(!("responseType" in new XMLHttpRequest))
{
alert("Your browser is not supported because it doesn't have XMLHttpRequest.responseType");
return;
}
var settings = {
load_devices: true
};
function load_local(file, type, cb)
{
set_title(file.name);
// SyncFileBuffer:
// - loads the whole disk image into memory, impossible for large files (more than 1GB)
// - can later serve get/set operations fast and synchronously
// - takes some time for first load, neglectable for small files (up to 100Mb)
//
// AsyncFileBuffer:
// - loads slices of the file asynchronously as requested
// - slower get/set
// Heuristics: If file is smaller than 64M, use SyncFileBuffer
if(file.size < 64 * 1024 * 1024)
{
var loader = new v86util.SyncFileBuffer(file);
loader.onprogress = show_progress.bind(this, { msg: "Loading disk image into memory" });
}
else
{
var loader = new v86util.AsyncFileBuffer(file);
}
loader.onload = function()
{
switch(type)
{
case "floppy":
settings.fda = loader;
break;
case "hd":
settings.hda = loader;
break;
case "cdrom":
settings.cdrom = loader;
break;
}
cb();
}
loader.load();
}
$("toggle_mouse").onclick = function()
{
var mouse_adapter = settings.mouse_adapter;
if(mouse_adapter)
{
var state = mouse_adapter.emu_enabled = !mouse_adapter.emu_enabled;
$("toggle_mouse").value = (state ? "Dis" : "En") + "able mouse";
}
};
$("lock_mouse").onclick = function()
{
var mouse_adapter = settings.mouse_adapter;
if(mouse_adapter && !mouse_adapter.emu_enabled)
{
$("toggle_mouse").onclick();
}
lock_mouse(document.body);
$("lock_mouse").blur();
};
var biosfile = DEBUG ? "seabios-debug.bin" : "seabios.bin";
var vgabiosfile = DEBUG ? "vgabios-0.7a.debug.bin" : "bochs-vgabios-0.7a.bin";
v86util.load_file("bios/" + biosfile, function(img)
{
settings.bios = img;
start_emulation();
});
v86util.load_file("bios/" + vgabiosfile, function(img)
{
settings.vga_bios = img;
start_emulation();
});
$("start_emulation").onclick = function()
{
$("boot_options").style.display = "none";
var images = [];
if($("floppy_image").files.length)
{
images.push({
file: $("floppy_image").files[0],
type: "floppy",
});
}
if($("cd_image").files.length)
{
images.push({
file: $("cd_image").files[0],
type: "cdrom",
});
}
if($("hd_image").files.length)
{
images.push({
file: $("hd_image").files[0],
type: "hd",
});
}
var cont = after(images.length, function(result)
{
set_profile("custom");
start_emulation({
settings: settings,
done: function(e) { e.run(); },
});
});
images.forEach(function(image)
{
load_local(image.file, image.type, cont);
});
};
if(DEBUG)
{
debug_onload(settings);
}
var oses = [
//{
// id: "archlinux",
// state: "http://localhost/v86-images/v86state.bin",
// //size: 137 * 1024 * 1024,
// size: 75550474,
// name: "Arch Linux",
// memory_size: 64 * 1024 * 1024,
// vga_memory_size: 8 * 1024 * 1024,
// async_hda: "http://localhost/v86-images/arch3.img",
// async_hda_size: 8 * 1024 * 1024 * 1024,
// filesystem: {
// basefs: "http://localhost/v86-images/fs.json",
// baseurl: "http://localhost/v86-images/arch/",
// },
//},
{
id: "freedos",
fda: "images/freedos722.img",
size: 737280,
name: "FreeDOS",
},
{
id: "windows1",
fda: "images/windows101.img",
size: 1474560,
name: "Windows",
},
{
id: "linux26",
cdrom: "images/linux.iso",
size: 5666816,
name: "Linux",
},
//{
// id: "nanolinux",
// cdrom: "images/nanolinux-1.2.iso",
// size: 14047232,
// name: "Nanolinux",
//},
{
id: "kolibrios",
fda: "images/kolibri.img",
size: 1474560,
name: "KolibriOS",
},
{
id: "openbsd",
fda: "images/openbsd.img",
size: 1474560,
name: "OpenBSD",
},
{
id: "solos",
fda: "images/os8.dsk",
size: 1474560,
name: "Sol OS",
},
];
var query_args = get_query_arguments();
var profile = query_args["profile"];
for(var i = 0; i < oses.length; i++)
{
var infos = oses[i];
var dom_id = "start_" + infos.id;
$(dom_id).onclick = function(infos, dom_id)
{
set_profile(infos.id);
$(dom_id).blur();
start_profile(infos);
}.bind(this, infos, dom_id);
if(profile === infos.id)
{
start_profile(infos);
return;
}
}
function start_profile(infos)
{
var message = { msg: "Downloading image", total: infos.size };
var image = infos.state || infos.fda || infos.cdrom;
var start = after(1, function(result)
{
loaded(infos, settings, result.buffer);
});
v86util.load_file(
image,
function(buffer)
{
start({ buffer: buffer });
},
show_progress.bind(this, message)
);
if(infos.filesystem)
{
settings.fs9p = new FS(infos.filesystem.baseurl);
if(infos.filesystem.basefs)
{
start.count++;
settings.fs9p.OnLoaded = start;
settings.fs9p.LoadFilesystem({
lazyloadimages: [],
earlyload: [],
basefsURL: infos.filesystem.basefs,
});
}
}
set_title(infos.name);
$("boot_options").style.display = "none";
}
function loaded(infos, settings, buffer)
{
settings.memory_size = infos.memory_size;
settings.vga_memory_size = infos.vga_memory_size;
if(infos.async_hda)
{
settings.hda = new v86util.AsyncXHRBuffer(
infos.async_hda,
512,
infos.async_hda_size
);
}
if(infos.fda)
{
settings.fda = new SyncBuffer(buffer);
}
else if(infos.cdrom)
{
settings.cdrom = new SyncBuffer(buffer);
}
start_emulation({
settings: settings,
done: function(emulator)
{
if(infos.state)
{
$("reset").style.display = "none";
emulator.restore_state(buffer);
}
//emulator.send("cpu-run");
emulator.run();
if(query_args["c"])
{
var cmd = query_args["c"] + "\n";
for(var i = 0; i < cmd.length; i++)
{
settings.serial_adapter.send_char(cmd.charCodeAt(i));
}
}
}
});
}
}
function debug_onload(settings)
{
// called on window.onload, in debug mode
//settings.fs9p = new FS("http://localhost/v86-images/arch/");
//settings.fs9p.LoadFilesystem({
// lazyloadimages: [],
// earlyload: [],
// basefsURL: "http://localhost/v86-images/fs.json",
//});
$("restore_state").onchange = function()
{
};
$("start_test").onclick = function()
{
};
var log_levels = document.getElementById("log_levels");
for(var i = 0; i < LOG_NAMES.length; i++)
{
var mask = LOG_NAMES[i][0];
if(mask === 1)
continue;
var name = LOG_NAMES[i][1].toLowerCase(),
input = document.createElement("input"),
label = document.createElement("label");
input.type = "checkbox";
label.htmlFor = input.id = "log_" + name;
if(LOG_LEVEL & mask)
{
input.checked = true;
}
input.mask = mask;
label.appendChild(input);
label.appendChild(document.createTextNode(name + " "));
log_levels.appendChild(label);
}
log_levels.onchange = function(e)
{
var target = e.target,
mask = target.mask;
if(target.checked)
{
LOG_LEVEL |= mask;
}
else
{
LOG_LEVEL &= ~mask;
}
};
}
window.addEventListener("load", onload, false);
// old webkit fires popstate on every load, fuck webkit
// https://code.google.com/p/chromium/issues/detail?id=63040
window.addEventListener("load", function()
{
setTimeout(function()
{
window.addEventListener("popstate", onpopstate);
}, 0);
});
// works in firefox and chromium
if(document.readyState === "complete")
{
onload();
}
var start_emulation = after(3, function(result)
{
var settings = result.settings;
dbg_assert(settings.bios && settings.vga_bios);
//var worker = new Worker("src/browser/worker.js");
//var adapter_bus = WorkerBus.init(worker);
var bus = Bus.create();
var adapter_bus = bus[0];
var device_bus = bus[1];
var emulator = new v86(device_bus);
if(DEBUG)
{
debug_start(emulator);
}
// avoid warnings
settings.fdb = undefined;
settings.screen_adapter = new ScreenAdapter($("screen_container"), adapter_bus);;
settings.keyboard_adapter = new KeyboardAdapter(adapter_bus);
settings.mouse_adapter = new MouseAdapter(adapter_bus);
settings.boot_order = parseInt($("boot_order").value, 16);
settings.serial_adapter = new SerialAdapter($("serial"), adapter_bus);
//settings.serial_adapter = new ModemAdapter();
//settings.network_adapter = new NetworkAdapter("ws://localhost:8001/", adapter_bus);
//settings.network_adapter = new NetworkAdapter("ws://relay.widgetry.org/", adapter_bus);
if(!settings.memory_size)
{
var memory_size = parseInt($("memory_size").value, 10) * 1024 * 1024;
if(memory_size >= 16 * 1024 * 1024 && memory_size < 2048 * 1024 * 1024)
{
settings.memory_size = memory_size;
}
else
{
alert("Invalid memory size - ignored.");
settings.memory_size = 32 * 1024 * 1024;
}
}
if(!settings.vga_memory_size)
{
var video_memory_size = parseInt($("video_memory_size").value, 10) * 1024 * 1024;
if(video_memory_size > 64 * 1024 && video_memory_size < 2048 * 1024 * 1024)
{
settings.vga_memory_size = video_memory_size;
}
else
{
alert("Invalid video memory size - ignored.");
settings.vga_memory_size = 8 * 1024 * 1024;
}
}
init_ui(settings, emulator);
emulator.init(settings);
//settings.fs9p = undefined;
//settings.fda = undefined;
//adapter_bus.send("cpu-init", settings);
//setTimeout(function()
//{
result.done(emulator);
//}, 100);
});
function init_ui(settings, emulator)
{
$("boot_options").style.display = "none";
$("loading").style.display = "none";
$("runtime_options").style.display = "block";
$("runtime_infos").style.display = "block";
document.getElementsByClassName("phone_keyboard")[0].style.display = "block";
if($("news"))
{
$("news").style.display = "none";
}
var running = true;
$("run").onclick = function()
{
if(running)
{
running_time += Date.now() - last_tick;
$("run").value = "Run";
emulator.stop();
}
else
{
$("run").value = "Pause";
emulator.run();
last_tick = Date.now();
}
running = !running;
$("run").blur();
};
$("exit").onclick = function()
{
location.href = location.pathname;
};
var time = $("running_time"),
ips = $("speed"),
avg_ips = $("avg_speed"),
last_tick = Date.now(),
running_time = 0,
summed_ips = 0,
last_instr_counter = 0;
function update_info()
{
if(!running)
{
setTimeout(update_info, 1000);
return;
}
var now = Date.now(),
last_ips = (emulator.cpu.timestamp_counter - last_instr_counter) / 1000 | 0;
summed_ips += last_ips
running_time += now - last_tick;
last_tick = now;
ips.textContent = last_ips;
avg_ips.textContent = summed_ips / running_time * 1000 | 0;
time.textContent = time2str(running_time / 1000 | 0);
last_instr_counter = emulator.cpu.timestamp_counter;
setTimeout(update_info, 1000);
}
function update_other_info()
{
if(!running)
{
setTimeout(update_other_info, 1000);
return;
}
var devices = emulator.cpu.devices;
var vga_stats = devices.vga.stats;
if(vga_stats.is_graphical)
{
$("info_vga_mode").textContent = "graphical";
$("info_res").textContent = vga_stats.res_x + "x" + vga_stats.res_y;
$("info_bpp").textContent = vga_stats.bpp;
}
else
{
$("info_vga_mode").textContent = "text";
$("info_res").textContent = "-";
$("info_bpp").textContent = "-";
}
if(settings.mouse_adapter)
{
$("info_mouse_enabled").textContent = settings.mouse_adapter.enabled ? "Yes" : "No";
}
if(devices.hda)
{
var hda_stats = devices.hda.stats;
$("info_hda_sectors_read").textContent = hda_stats.sectors_read;
$("info_hda_bytes_read").textContent = hda_stats.bytes_read;
$("info_hda_sectors_written").textContent = hda_stats.sectors_written;
$("info_hda_bytes_written").textContent = hda_stats.bytes_written;
$("info_hda_status").textContent = hda_stats.loading ? "Loading ..." : "Idle";
}
else
{
$("info_hda").style.display = "none";
}
if(devices.cdrom)
{
var cdrom_stats = devices.cdrom.stats;
$("info_cdrom_sectors_read").textContent = cdrom_stats.sectors_read;
$("info_cdrom_bytes_read").textContent = cdrom_stats.bytes_read;
$("info_cdrom_status").textContent = cdrom_stats.loading ? "Loading ..." : "Idle";
}
else
{
$("info_cdrom").style.display = "none";
}
setTimeout(update_other_info, 1000);
}
setTimeout(update_info, 1000);
setTimeout(update_other_info, 0);
$("reset").onclick = function()
{
emulator.restart();
$("reset").blur();
};
// writable image types
var image_types = ["hda", "hdb", "fda", "fdb"];
for(var i = 0; i < image_types.length; i++)
{
var elem = $("get_" + image_types[i] + "_image");
var obj = settings[image_types[i]];
if(obj && obj.byteLength < 16 * 1024 * 1024)
{
elem.onclick = (function(type)
{
obj.get_buffer(function(b)
{
dump_file(b, type + ".img");
});
this.blur();
}).bind(elem, image_types[i]);
}
else
{
elem.style.display = "none";
}
}
$("ctrlaltdel").onclick = function()
{
var ps2 = emulator.cpu.devices.ps2;
ps2.kbd_send_code(0x1D); // ctrl
ps2.kbd_send_code(0x38); // alt
ps2.kbd_send_code(0x53); // delete
// break codes
ps2.kbd_send_code(0x1D | 0x80);
ps2.kbd_send_code(0x38 | 0x80);
ps2.kbd_send_code(0x53 | 0x80);
$("ctrlaltdel").blur();
};
$("alttab").onclick = function()
{
var ps2 = emulator.cpu.devices.ps2;
ps2.kbd_send_code(0x38); // alt
ps2.kbd_send_code(0x0F); // tab
ps2.kbd_send_code(0x38 | 0x80);
ps2.kbd_send_code(0x0F | 0x80);
$("alttab").blur();
};
$("scale").onchange = function()
{
var n = parseFloat(this.value);
if(n || n > 0)
{
settings.screen_adapter.set_scale(n, n);
}
};
$("fullscreen").onclick = function()
{
var elem = document.getElementById("screen_container"),
// bracket notation because otherwise they get renamed by closure compiler
fn = elem["requestFullScreen"] ||
elem["webkitRequestFullscreen"] ||
elem["mozRequestFullScreen"] ||
elem["msRequestFullScreen"];
if(fn)
{
fn.call(elem);
// This is necessary, because otherwise chromium keyboard doesn't work anymore.
// Might (but doesn't seem to) break something else
document.getElementsByClassName("phone_keyboard")[0].focus();
}
lock_mouse(elem);
};
$("screen_container").onclick = function()
{
// allow text selection
if(window.getSelection().isCollapsed)
{
document.getElementsByClassName("phone_keyboard")[0].focus();
}
};
$("take_screenshot").onclick = function()
{
settings.screen_adapter.make_screenshot();
$("take_screenshot").blur();
};
if(settings.serial_adapter)
{
$("serial").style.display = "block";
}
window.addEventListener("keydown", ctrl_w_rescue, false);
window.addEventListener("keyup", ctrl_w_rescue, false);
window.addEventListener("blur", ctrl_w_rescue, false);
function ctrl_w_rescue(e)
{
if(e.ctrlKey)
{
window.onbeforeunload = function()
{
window.onbeforeunload = null;
return "CTRL-W cannot be sent to the emulator.";
}
}
else
{
window.onbeforeunload = null;
}
}
}
function debug_start(emulator)
{
// called as soon as soon as emulation is started, in debug mode
var debug = emulator.cpu.debug;
$("step").onclick = debug.step.bind(debug);
$("run_until").onclick = debug.run_until.bind(debug);
$("debugger").onclick = debug.debugger.bind(debug);
$("dump_gdt").onclick = debug.dump_gdt_ldt.bind(debug);
$("dump_idt").onclick = debug.dump_idt.bind(debug);
$("dump_regs").onclick = debug.dump_regs.bind(debug);
$("dump_pt").onclick = debug.dump_page_directory.bind(debug);
$("dump_instructions").onclick = debug.dump_instructions.bind(debug);
$("dump_instructions_file").onclick = function()
{
var ins = debug.get_instructions();
if(ins)
{
dump_file(ins, "trace.txt");
}
};
$("memory_dump").onclick = function()
{
dump_file(debug.get_memory_dump(), "v86-memory.bin");
$("memory_dump").blur();
};
$("save_state").onclick = function()
{
dump_file(emulator.save_state(), "v86-state.bin");
$("save_state").blur();
};
window.emulator = emulator;
}
function onpopstate(e)
{
location.reload();
}
function set_profile(prof)
{
if(window.history.pushState)
{
window.history.pushState({ profile: prof }, "", "?profile=" + prof);
}
}
})();