2017-01-03 15:43:40 +00:00
|
|
|
"use strict";
|
2016-12-16 01:21:51 +00:00
|
|
|
|
2017-01-04 21:07:59 +00:00
|
|
|
var FileStore = function(storage, prefix) {
|
|
|
|
var self = this;
|
|
|
|
this.saveFile = function(name, text) {
|
|
|
|
storage.setItem(prefix + name, text);
|
|
|
|
}
|
|
|
|
this.loadFile = function(name) {
|
|
|
|
return storage.getItem(prefix + name) || storage.getItem(name);
|
|
|
|
}
|
|
|
|
this.getFiles = function(prefix2) {
|
|
|
|
var files = [];
|
|
|
|
for (var i = 0; i < storage.length; i++) {
|
|
|
|
var key = storage.key(i);
|
|
|
|
if (key.startsWith(prefix + prefix2)) {
|
|
|
|
var name = key.substring(prefix.length + prefix2.length);
|
|
|
|
files.push(name);
|
|
|
|
}
|
|
|
|
else if (key.startsWith(prefix2)) {
|
|
|
|
var name = key.substring(prefix2.length);
|
|
|
|
files.push(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
this.deleteFile = function(name) {
|
|
|
|
storage.removeItem(name);
|
|
|
|
storage.removeItem('local/' + name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-03 01:42:15 +00:00
|
|
|
// 8bitworkshop IDE user interface
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
var worker = new Worker("./src/worker/workermain.js");
|
|
|
|
var current_output = null;
|
2017-01-03 20:52:38 +00:00
|
|
|
var current_preset_index = -1; // TODO: use URL
|
2016-12-16 01:21:51 +00:00
|
|
|
var current_preset_id = null;
|
|
|
|
var offset2line = null;
|
|
|
|
var line2offset = null;
|
2017-01-06 14:49:07 +00:00
|
|
|
var pcvisits;
|
2016-12-16 01:21:51 +00:00
|
|
|
var trace_pending_at_pc;
|
2017-01-04 21:07:59 +00:00
|
|
|
var store;
|
2016-12-16 01:21:51 +00:00
|
|
|
|
2017-01-03 20:52:38 +00:00
|
|
|
var PRESETS, platform, platform_id;
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
var CODE = 'code1';
|
|
|
|
var editor = CodeMirror(document.getElementById('editor'), {
|
|
|
|
mode: '6502',
|
|
|
|
theme: 'mbo',
|
|
|
|
lineNumbers: true,
|
|
|
|
tabSize: 8,
|
|
|
|
gutters: ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"],
|
|
|
|
});
|
2017-01-08 15:51:19 +00:00
|
|
|
var disasmview = CodeMirror(document.getElementById('disassembly'), {
|
|
|
|
mode: '6502',
|
|
|
|
theme: 'cobalt',
|
|
|
|
tabSize: 8,
|
|
|
|
readOnly: true,
|
|
|
|
styleActiveLine: true,
|
|
|
|
gutters: ["gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"],
|
|
|
|
});
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
editor.on('changes', function(ed, changeobj) {
|
2017-01-03 15:43:40 +00:00
|
|
|
var text = editor.getValue() || "";
|
2016-12-16 01:21:51 +00:00
|
|
|
setCode(text);
|
|
|
|
});
|
|
|
|
|
|
|
|
function getCurrentPresetTitle() {
|
2017-01-03 20:52:38 +00:00
|
|
|
if (current_preset_index < 0)
|
2016-12-16 01:21:51 +00:00
|
|
|
return "ROM";
|
|
|
|
else
|
2017-01-03 20:52:38 +00:00
|
|
|
return PRESETS[current_preset_index].title || PRESETS[current_preset_index].name || "ROM";
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function setLastPreset(id) {
|
2017-01-03 20:52:38 +00:00
|
|
|
localStorage.setItem("__lastplatform", platform_id);
|
2017-01-12 03:28:29 +00:00
|
|
|
localStorage.setItem("__lastid_"+platform_id, id);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function updatePreset(current_preset_id, text) {
|
|
|
|
if (text.trim().length) {
|
2017-01-04 21:07:59 +00:00
|
|
|
store.saveFile(current_preset_id, text);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadCode(text) {
|
|
|
|
editor.setValue(text);
|
|
|
|
current_output = null;
|
|
|
|
setCode(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadFile(fileid, filename, index) {
|
|
|
|
current_preset_id = fileid;
|
2017-01-03 20:52:38 +00:00
|
|
|
current_preset_index = index;
|
2017-01-04 21:07:59 +00:00
|
|
|
var text = store.loadFile(fileid)|| "";
|
2016-12-16 01:21:51 +00:00
|
|
|
if (text) {
|
|
|
|
loadCode(text);
|
|
|
|
setLastPreset(fileid);
|
|
|
|
} else if (!text && index >= 0) {
|
|
|
|
filename += ".a";
|
|
|
|
console.log("Loading preset", fileid, filename, index, PRESETS[index]);
|
|
|
|
if (text.length == 0) {
|
|
|
|
console.log("Fetching", filename);
|
|
|
|
$.get( filename, function( text ) {
|
|
|
|
console.log("GET",text.length,'bytes');
|
|
|
|
loadCode(text);
|
|
|
|
setLastPreset(fileid);
|
|
|
|
}, 'text');
|
|
|
|
}
|
|
|
|
} else {
|
2017-01-03 20:52:38 +00:00
|
|
|
$.get( "presets/"+platform_id+"/skeleton.a", function( text ) {
|
2016-12-16 01:21:51 +00:00
|
|
|
loadCode(text);
|
|
|
|
setLastPreset(fileid);
|
|
|
|
updatePreset(fileid, text);
|
|
|
|
}, 'text');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadPreset(preset_id) {
|
|
|
|
// TODO
|
|
|
|
var index = parseInt(preset_id+"");
|
|
|
|
for (var i=0; i<PRESETS.length; i++)
|
|
|
|
if (PRESETS[i].id == preset_id)
|
|
|
|
index = i;
|
|
|
|
index = (index + PRESETS.length) % PRESETS.length;
|
|
|
|
if (index >= 0) {
|
|
|
|
// load the preset
|
2017-01-03 20:52:38 +00:00
|
|
|
loadFile(preset_id, "presets/" + platform_id + "/" + PRESETS[index].id, index);
|
2016-12-16 01:21:51 +00:00
|
|
|
} else {
|
|
|
|
// no preset found? load local
|
2017-01-03 20:52:38 +00:00
|
|
|
loadFile(preset_id, "local/" + platform_id + "/" + preset_id, -1);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function gotoPresetAt(index) {
|
|
|
|
var index = (index + PRESETS.length) % PRESETS.length;
|
|
|
|
qs['file'] = PRESETS[index].id;
|
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
}
|
|
|
|
|
|
|
|
function gotoPresetNamed(id) {
|
|
|
|
if (id.startsWith("_")) {
|
|
|
|
var result = eval(id+"()");
|
|
|
|
console.log(id, result);
|
|
|
|
if (!result) {
|
|
|
|
updateSelector();
|
|
|
|
}
|
|
|
|
} else {
|
2017-01-04 21:07:59 +00:00
|
|
|
qs['platform'] = platform_id;
|
2016-12-16 01:21:51 +00:00
|
|
|
qs['file'] = id;
|
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _createNewFile(e) {
|
|
|
|
var filename = prompt("Create New File", "newfile.a");
|
|
|
|
if (filename && filename.length) {
|
|
|
|
if (filename.indexOf(".") < 0) {
|
|
|
|
filename += ".a";
|
|
|
|
}
|
2017-01-04 21:07:59 +00:00
|
|
|
qs['file'] = "local/" + filename;
|
2016-12-16 01:21:51 +00:00
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
}
|
2016-12-30 23:51:15 +00:00
|
|
|
return true;
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function _shareFile(e) {
|
|
|
|
if (current_output == null) {
|
2016-12-30 23:51:15 +00:00
|
|
|
alert("Please fix errors before sharing.");
|
|
|
|
return true;
|
|
|
|
}
|
2016-12-16 01:21:51 +00:00
|
|
|
var text = editor.getValue();
|
|
|
|
console.log("POST",text.length,'bytes');
|
|
|
|
$.post({
|
|
|
|
url: 'share.php',
|
|
|
|
data: {
|
|
|
|
'platform':'vcs', /// TODO
|
|
|
|
'filename':current_preset_id.split('/').pop(),
|
|
|
|
'text':text,
|
|
|
|
},
|
|
|
|
error: function(e) {
|
|
|
|
console.log(e);
|
|
|
|
alert("Error sharing file.");
|
|
|
|
},
|
|
|
|
success: function(result) {
|
2016-12-30 23:51:15 +00:00
|
|
|
var sharekey = result['key'];
|
|
|
|
var url = "http://8bitworkshop.com/?sharekey=" + sharekey;
|
|
|
|
window.prompt("Copy link to clipboard (Ctrl+C, Enter)", url);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
});
|
2016-12-30 23:51:15 +00:00
|
|
|
return true;
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function _resetPreset(e) {
|
2017-01-03 20:52:38 +00:00
|
|
|
if (current_preset_index < 0) {
|
2017-01-03 01:42:15 +00:00
|
|
|
alert("Can only reset built-in file examples.")
|
2017-01-03 20:52:38 +00:00
|
|
|
} else if (confirm("Reset '" + PRESETS[current_preset_index].name + "' to default?")) {
|
2016-12-16 01:21:51 +00:00
|
|
|
qs['reset'] = '1';
|
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
}
|
2016-12-30 23:51:15 +00:00
|
|
|
return true;
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function populateExamples(sel) {
|
2016-12-17 15:13:53 +00:00
|
|
|
sel.append($("<option />").text("--------- Chapters ---------"));
|
2016-12-16 01:21:51 +00:00
|
|
|
for (var i=0; i<PRESETS.length; i++) {
|
|
|
|
var preset = PRESETS[i];
|
|
|
|
var name = preset.chapter + ". " + preset.name;
|
|
|
|
sel.append($("<option />").val(preset.id).text(name).attr('selected',preset.id==current_preset_id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function populateLocalFiles(sel) {
|
|
|
|
sel.append($("<option />").text("------- Local Files -------"));
|
2017-01-04 21:07:59 +00:00
|
|
|
var filenames = store.getFiles("local/");
|
|
|
|
for (var i = 0; i < filenames.length; i++) {
|
|
|
|
var name = filenames[i];
|
|
|
|
var key = "local/" + name;
|
|
|
|
sel.append($("<option />").val(key).text(name).attr('selected',key==current_preset_id));
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function populateSharedFiles(sel) {
|
|
|
|
sel.append($("<option />").text("--------- Shared ---------"));
|
2017-01-04 21:07:59 +00:00
|
|
|
var filenames = store.getFiles("shared/");
|
|
|
|
for (var i = 0; i < filenames.length; i++) {
|
|
|
|
var name = filenames[i];
|
|
|
|
var key = "shared/" + name;
|
|
|
|
sel.append($("<option />").val(key).text(name).attr('selected',key==current_preset_id));
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateSelector() {
|
|
|
|
var sel = $("#preset_select").empty();
|
|
|
|
populateLocalFiles(sel);
|
|
|
|
populateSharedFiles(sel);
|
|
|
|
populateExamples(sel);
|
|
|
|
// set click handlers
|
|
|
|
sel.off('change').change(function(e) {
|
|
|
|
gotoPresetNamed($(this).val());
|
|
|
|
});
|
|
|
|
$("#preset_prev").off('click').click(function() {
|
2017-01-03 20:52:38 +00:00
|
|
|
gotoPresetAt(current_preset_index - 1);
|
2016-12-16 01:21:51 +00:00
|
|
|
});
|
|
|
|
$("#preset_next").off('click').click(function() {
|
2017-01-03 20:52:38 +00:00
|
|
|
gotoPresetAt(current_preset_index + 1);
|
2016-12-16 01:21:51 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-06 14:49:07 +00:00
|
|
|
function getToolForFilename(fn) {
|
|
|
|
if (fn.endsWith(".pla")) return "plasm";
|
|
|
|
if (fn.endsWith(".c")) return "cc65";
|
|
|
|
if (fn.endsWith(".s")) return "ca65";
|
|
|
|
return "dasm";
|
|
|
|
}
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
function setCode(text) {
|
2017-01-06 14:49:07 +00:00
|
|
|
worker.postMessage({code:text, tool:getToolForFilename(current_preset_id)});
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function arrayCompare(a,b) {
|
|
|
|
if (a == null && b == null) return true;
|
|
|
|
if (a == null) return false;
|
|
|
|
if (b == null) return false;
|
|
|
|
if (a.length != b.length) return false;
|
|
|
|
for (var i=0; i<a.length; i++)
|
|
|
|
if (a[i] != b[i])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
worker.onmessage = function(e) {
|
|
|
|
// errors?
|
2017-01-07 02:09:41 +00:00
|
|
|
var toolbar = $("#controls_top");
|
2016-12-16 01:21:51 +00:00
|
|
|
if (e.data.listing.errors.length > 0) {
|
2017-01-06 14:49:07 +00:00
|
|
|
toolbar.addClass("has-errors");
|
2016-12-16 01:21:51 +00:00
|
|
|
editor.clearGutter("gutter-info");
|
|
|
|
for (info of e.data.listing.errors) {
|
|
|
|
var div = document.createElement("div");
|
2017-01-03 01:42:15 +00:00
|
|
|
div.setAttribute("class", "tooltipbox tooltiperror");
|
2016-12-16 01:21:51 +00:00
|
|
|
div.style.color = '#ff3333'; // TODO
|
|
|
|
div.appendChild(document.createTextNode("\u24cd"));
|
|
|
|
var tooltip = document.createElement("span");
|
|
|
|
tooltip.setAttribute("class", "tooltiptext");
|
|
|
|
tooltip.appendChild(document.createTextNode(info.msg));
|
|
|
|
div.appendChild(tooltip);
|
|
|
|
editor.setGutterMarker(info.line-1, "gutter-info", div);
|
|
|
|
}
|
|
|
|
current_output = null;
|
|
|
|
} else {
|
2017-01-06 14:49:07 +00:00
|
|
|
toolbar.removeClass("has-errors");
|
2017-01-03 15:43:40 +00:00
|
|
|
updatePreset(current_preset_id, editor.getValue()); // update persisted entry
|
2016-12-16 01:21:51 +00:00
|
|
|
// load ROM
|
2017-01-06 14:49:07 +00:00
|
|
|
var rom = e.data.output;
|
2016-12-16 01:21:51 +00:00
|
|
|
var rom_changed = rom && !arrayCompare(rom, current_output);
|
|
|
|
if (rom_changed) {
|
|
|
|
try {
|
|
|
|
resume();
|
|
|
|
//console.log("Loading ROM length", rom.length);
|
2016-12-31 16:05:22 +00:00
|
|
|
platform.loadROM(getCurrentPresetTitle(), rom);
|
2016-12-16 01:21:51 +00:00
|
|
|
current_output = rom;
|
2017-01-06 14:49:07 +00:00
|
|
|
pcvisits = {};
|
2016-12-16 01:21:51 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.log(e); // TODO
|
|
|
|
current_output = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rom_changed || trace_pending_at_pc) {
|
|
|
|
// update editor annotations
|
|
|
|
editor.clearGutter("gutter-info");
|
|
|
|
editor.clearGutter("gutter-bytes");
|
|
|
|
editor.clearGutter("gutter-offset");
|
|
|
|
editor.clearGutter("gutter-clock");
|
|
|
|
offset2line = {};
|
|
|
|
line2offset = {};
|
2017-01-03 15:43:40 +00:00
|
|
|
for (var info of e.data.listing.lines) {
|
2016-12-16 01:21:51 +00:00
|
|
|
if (info.offset) {
|
|
|
|
var textel = document.createTextNode(info.offset.toString(16));
|
|
|
|
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
|
|
|
|
offset2line[info.offset] = info.line;
|
|
|
|
line2offset[info.line] = info.offset;
|
|
|
|
}
|
|
|
|
if (info.insns) {
|
|
|
|
var insnstr = info.insns.length > 8 ? ("...") : info.insns;
|
|
|
|
var textel = document.createTextNode(insnstr);
|
|
|
|
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
|
|
|
|
if (info.iscode) {
|
|
|
|
var opcode = parseInt(info.insns.split()[0], 16);
|
2016-12-31 16:05:22 +00:00
|
|
|
var meta = platform.getOpcodeMetadata(opcode, info.offset);
|
2016-12-16 01:21:51 +00:00
|
|
|
var clockstr = meta.minCycles+"";
|
|
|
|
var textel = document.createTextNode(clockstr);
|
|
|
|
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (trace_pending_at_pc) {
|
|
|
|
showLoopTimingForPC(trace_pending_at_pc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
trace_pending_at_pc = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function findLineForOffset(PC) {
|
|
|
|
if (offset2line) {
|
|
|
|
for (var i=0; i<256; i++) {
|
|
|
|
var line = offset2line[PC];
|
|
|
|
if (line) {
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
PC--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setCurrentLine(line) {
|
|
|
|
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
|
|
|
|
}
|
|
|
|
|
|
|
|
function hex(v, nd) {
|
|
|
|
try {
|
|
|
|
if (!nd) nd = 2;
|
|
|
|
var s = v.toString(16).toUpperCase();
|
|
|
|
while (s.length < nd)
|
|
|
|
s = "0" + s;
|
|
|
|
return s;
|
|
|
|
} catch (e) {
|
|
|
|
return v+"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function decodeFlags(c, flags) {
|
|
|
|
var s = "";
|
|
|
|
s += c.N ? " N" : " -";
|
|
|
|
s += c.V ? " V" : " -";
|
|
|
|
s += c.D ? " D" : " -";
|
|
|
|
s += c.Z ? " Z" : " -";
|
|
|
|
s += c.C ? " C" : " -";
|
|
|
|
// s += c.I ? " I" : " -";
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
function cpuStateToLongString(c) {
|
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + " " + getTIAPosString() + "\n"
|
|
|
|
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
|
|
|
|
+ " X " + hex(c.X) + "\n"
|
|
|
|
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
|
|
|
|
}
|
|
|
|
function getTIAPosString() {
|
2016-12-31 16:05:22 +00:00
|
|
|
var pos = platform.getRasterPosition();
|
|
|
|
return "V" + pos.y + " H" + pos.x;
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var lastDebugInfo;
|
2017-01-06 14:49:07 +00:00
|
|
|
var lastDebugState;
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
function highlightDifferences(s1, s2) {
|
|
|
|
var split1 = s1.split(/(\S+\s+)/).filter(function(n) {return n});
|
|
|
|
var split2 = s2.split(/(\S+\s+)/).filter(function(n) {return n});
|
|
|
|
var i = 0;
|
|
|
|
var j = 0;
|
|
|
|
var result = "";
|
|
|
|
while (i < split1.length && j < split2.length) {
|
|
|
|
var w1 = split1[i];
|
|
|
|
var w2 = split2[j];
|
|
|
|
if (w2 && w2.indexOf("\n") >= 0) {
|
|
|
|
while (i < s1.length && split1[i].indexOf("\n") < 0)
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (w1 != w2) {
|
|
|
|
w2 = '<span class="hilite">' + w2 + '</span>';
|
|
|
|
}
|
|
|
|
result += w2;
|
|
|
|
i++;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
while (j < split2.length) {
|
|
|
|
result += split2[j++];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function showMemory(state) {
|
|
|
|
var s = "";
|
|
|
|
if (state) {
|
|
|
|
s = cpuStateToLongString(state.c);
|
|
|
|
s += "\n";
|
2017-01-03 01:42:15 +00:00
|
|
|
var ram = platform.getRAMForState(state);
|
2017-01-07 18:05:02 +00:00
|
|
|
var ramlen = ram.length <= 128 ? 128 : 256; // TODO
|
|
|
|
var ramofs = ram.length == 128 ? 0x80 : 0;
|
2017-01-03 01:42:15 +00:00
|
|
|
// TODO: show scrollable RAM for other platforms
|
2017-01-07 18:05:02 +00:00
|
|
|
for (var ofs=0; ofs<ramlen; ofs+=0x10) {
|
|
|
|
s += '$' + hex(ofs+ramofs) + ':';
|
2016-12-16 01:21:51 +00:00
|
|
|
for (var i=0; i<0x10; i++) {
|
|
|
|
if (i == 8) s += " ";
|
|
|
|
s += " " + hex(ram[ofs+i]);
|
|
|
|
}
|
|
|
|
s += "\n";
|
|
|
|
}
|
|
|
|
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
|
|
|
|
$("#mem_info").show().html(hs);
|
|
|
|
lastDebugInfo = s;
|
|
|
|
} else {
|
|
|
|
$("#mem_info").hide();
|
|
|
|
lastDebugInfo = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setupBreakpoint() {
|
|
|
|
// TODO
|
2016-12-31 16:05:22 +00:00
|
|
|
platform.setupDebug(function(state) {
|
2017-01-06 14:49:07 +00:00
|
|
|
lastDebugState = state;
|
2016-12-16 01:21:51 +00:00
|
|
|
var PC = state.c.PC;
|
|
|
|
var line = findLineForOffset(PC);
|
|
|
|
if (line) {
|
|
|
|
console.log("BREAKPOINT", hex(PC), line);
|
|
|
|
setCurrentLine(line);
|
|
|
|
}
|
2017-01-06 14:49:07 +00:00
|
|
|
pcvisits[PC] = pcvisits[PC] ? pcvisits[PC]+1 : 1;
|
2016-12-16 01:21:51 +00:00
|
|
|
showMemory(state);
|
2017-01-06 14:49:07 +00:00
|
|
|
updateDisassembly();
|
2016-12-31 16:05:22 +00:00
|
|
|
});
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function pause() {
|
|
|
|
clearBreakpoint();
|
2016-12-31 16:05:22 +00:00
|
|
|
if (platform.isRunning()) {
|
|
|
|
platform.pause();
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function resume() {
|
|
|
|
clearBreakpoint();
|
2016-12-31 16:05:22 +00:00
|
|
|
if (! platform.isRunning()) {
|
|
|
|
platform.resume();
|
2016-12-16 01:21:51 +00:00
|
|
|
editor.setSelection(editor.getCursor());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function singleStep() {
|
|
|
|
setupBreakpoint();
|
2016-12-31 16:05:22 +00:00
|
|
|
platform.step();
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getCurrentLine() {
|
|
|
|
return editor.getCursor().line+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function runToCursor() {
|
|
|
|
setupBreakpoint();
|
|
|
|
var line = getCurrentLine();
|
|
|
|
var pc = line2offset[line];
|
|
|
|
if (pc) {
|
|
|
|
console.log("Run to", line, pc.toString(16));
|
2017-01-06 16:57:28 +00:00
|
|
|
platform.runEval(function(c) {
|
|
|
|
return c.PC == pc;
|
|
|
|
});
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-07 18:05:02 +00:00
|
|
|
function runUntilReturn() {
|
|
|
|
setupBreakpoint();
|
|
|
|
var depth = 1;
|
|
|
|
platform.runEval(function(c) {
|
|
|
|
if (depth <= 0 && c.T == 0)
|
|
|
|
return true;
|
|
|
|
if (c.o == 0x20)
|
|
|
|
depth++;
|
|
|
|
else if (c.o == 0x60 || c.o == 0x40)
|
|
|
|
--depth;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runStepBackwards() {
|
|
|
|
setupBreakpoint();
|
|
|
|
platform.stepBack();
|
|
|
|
}
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
function clearBreakpoint() {
|
2017-01-06 14:49:07 +00:00
|
|
|
lastDebugState = null;
|
2016-12-31 16:05:22 +00:00
|
|
|
platform.clearDebug();
|
2016-12-16 01:21:51 +00:00
|
|
|
$("#dbg_info").empty();
|
|
|
|
showMemory();
|
|
|
|
}
|
|
|
|
|
|
|
|
function getClockCountsAtPC(pc) {
|
2016-12-31 16:05:22 +00:00
|
|
|
var opcode = platform.readAddress(pc);
|
|
|
|
var meta = platform.getOpcodeMetadata(opcode, pc);
|
2016-12-16 01:21:51 +00:00
|
|
|
return meta; // minCycles, maxCycles
|
|
|
|
}
|
|
|
|
|
|
|
|
var pc2minclocks = {};
|
|
|
|
var pc2maxclocks = {};
|
|
|
|
var jsrresult = {};
|
|
|
|
var MAX_CLOCKS = 76*2;
|
|
|
|
|
|
|
|
function byte2signed(b) {
|
|
|
|
b &= 0xff;
|
|
|
|
return (b < 0x80) ? b : -(256-b);
|
|
|
|
}
|
|
|
|
|
|
|
|
// [taken, not taken]
|
|
|
|
var BRANCH_CONSTRAINTS = [
|
|
|
|
[{N:0},{N:1}],
|
|
|
|
[{N:1},{N:0}],
|
|
|
|
[{V:0},{V:1}],
|
|
|
|
[{V:1},{V:0}],
|
|
|
|
[{C:0},{C:1}],
|
|
|
|
[{C:1},{C:0}],
|
|
|
|
[{Z:0},{Z:1}],
|
|
|
|
[{Z:1},{Z:0}]
|
|
|
|
];
|
|
|
|
|
|
|
|
function constraintEquals(a,b) {
|
|
|
|
if (a == null || b == null)
|
|
|
|
return null;
|
|
|
|
for (var n in a) {
|
|
|
|
if (b[n] !== 'undefined')
|
|
|
|
return a[n] == b[n];
|
|
|
|
}
|
|
|
|
for (var n in b) {
|
|
|
|
if (a[n] !== 'undefined')
|
|
|
|
return a[n] == b[n];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _traceInstructions(pc, minclocks, maxclocks, subaddr, constraints) {
|
|
|
|
//console.log("trace", hex(pc), minclocks, maxclocks);
|
|
|
|
if (!minclocks) minclocks = 0;
|
|
|
|
if (!maxclocks) maxclocks = 0;
|
|
|
|
if (!constraints) constraints = {};
|
|
|
|
var modified = true;
|
|
|
|
var abort = false;
|
|
|
|
for (var i=0; i<1000 && modified && !abort; i++) {
|
|
|
|
modified = false;
|
|
|
|
var meta = getClockCountsAtPC(pc);
|
2016-12-31 16:05:22 +00:00
|
|
|
var lob = platform.readAddress(pc+1);
|
|
|
|
var hib = platform.readAddress(pc+2);
|
2016-12-16 01:21:51 +00:00
|
|
|
var addr = lob + (hib << 8);
|
|
|
|
var pc0 = pc;
|
|
|
|
if (!pc2minclocks[pc0] || minclocks < pc2minclocks[pc0]) {
|
|
|
|
pc2minclocks[pc0] = minclocks;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
if (!pc2maxclocks[pc0] || maxclocks > pc2maxclocks[pc0]) {
|
|
|
|
pc2maxclocks[pc0] = maxclocks;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
//console.log(hex(pc),minclocks,maxclocks,meta);
|
|
|
|
if (!meta.insnlength) {
|
|
|
|
console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pc += meta.insnlength;
|
|
|
|
var oldconstraints = constraints;
|
|
|
|
constraints = null;
|
|
|
|
// TODO: if jump to zero-page, maybe assume RTS?
|
|
|
|
switch (meta.opcode) {
|
|
|
|
/*
|
|
|
|
case 0xb9: // TODO: hack for zero page,y
|
|
|
|
if (addr < 0x100)
|
|
|
|
meta.maxCycles -= 1;
|
|
|
|
break;
|
|
|
|
*/
|
|
|
|
case 0x85:
|
|
|
|
if (lob == 0x2) { // STA WSYNC
|
|
|
|
minclocks = maxclocks = 0;
|
|
|
|
meta.minCycles = meta.maxCycles = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x20: // JSR
|
|
|
|
_traceInstructions(addr, minclocks, maxclocks, addr, constraints);
|
|
|
|
var result = jsrresult[addr];
|
|
|
|
if (result) {
|
|
|
|
minclocks = result.minclocks;
|
|
|
|
maxclocks = result.maxclocks;
|
|
|
|
} else {
|
|
|
|
console.log("No JSR result!", hex(pc), hex(addr));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x4c: // JMP
|
|
|
|
pc = addr; // TODO: make sure in ROM space
|
|
|
|
break;
|
|
|
|
case 0x60: // RTS
|
|
|
|
if (subaddr) {
|
|
|
|
// TODO: combine with previous result
|
|
|
|
var result = jsrresult[subaddr];
|
|
|
|
if (!result) {
|
|
|
|
result = {minclocks:minclocks, maxclocks:maxclocks};
|
|
|
|
} else {
|
|
|
|
result = {
|
|
|
|
minclocks:Math.min(minclocks,result.minclocks),
|
|
|
|
maxclocks:Math.max(maxclocks,result.maxclocks)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
jsrresult[subaddr] = result;
|
|
|
|
console.log("RTS", hex(pc), hex(subaddr), jsrresult[subaddr]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case 0x10: case 0x30: // branch
|
|
|
|
case 0x50: case 0x70:
|
|
|
|
case 0x90: case 0xB0:
|
|
|
|
case 0xD0: case 0xF0:
|
|
|
|
var newpc = pc + byte2signed(lob);
|
|
|
|
var crosspage = (pc>>8) != (newpc>>8);
|
|
|
|
if (!crosspage) meta.maxCycles--;
|
|
|
|
// TODO: other instructions might modify flags too
|
|
|
|
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode-0x10)/0x20)];
|
|
|
|
var cons0 = constraintEquals(oldconstraints, cons[0]);
|
|
|
|
var cons1 = constraintEquals(oldconstraints, cons[1]);
|
|
|
|
if (cons0 !== false) {
|
|
|
|
_traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
|
|
|
|
}
|
|
|
|
if (cons1 === false) {
|
|
|
|
console.log("abort", hex(pc), oldconstraints, cons[1]);
|
|
|
|
abort = true;
|
|
|
|
}
|
|
|
|
constraints = cons[1]; // not taken
|
|
|
|
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
|
|
|
|
break;
|
|
|
|
case 0x6c:
|
|
|
|
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// TODO: wraparound?
|
|
|
|
minclocks = Math.min(MAX_CLOCKS, minclocks + meta.minCycles);
|
|
|
|
maxclocks = Math.min(MAX_CLOCKS, maxclocks + meta.maxCycles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLoopTimingForPC(pc) {
|
|
|
|
pc2minclocks = {};
|
|
|
|
pc2maxclocks = {};
|
|
|
|
jsrresult = {};
|
|
|
|
// recurse through all traces
|
2016-12-31 16:05:22 +00:00
|
|
|
_traceInstructions(pc | platform.getOriginPC(), MAX_CLOCKS, MAX_CLOCKS);
|
2016-12-16 01:21:51 +00:00
|
|
|
// show the lines
|
|
|
|
for (var line in line2offset) {
|
|
|
|
var pc = line2offset[line];
|
|
|
|
var minclocks = pc2minclocks[pc];
|
|
|
|
var maxclocks = pc2maxclocks[pc];
|
|
|
|
if (minclocks>=0 && maxclocks>=0) {
|
|
|
|
var s;
|
|
|
|
if (maxclocks == minclocks)
|
|
|
|
s = minclocks + "";
|
|
|
|
else
|
|
|
|
s = minclocks + "-" + maxclocks;
|
|
|
|
if (maxclocks == MAX_CLOCKS)
|
|
|
|
s += "+";
|
|
|
|
var textel = document.createTextNode(s);
|
|
|
|
editor.setGutterMarker(line-1, "gutter-bytes", textel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function traceTiming() {
|
2016-12-31 16:05:22 +00:00
|
|
|
trace_pending_at_pc = platform.getOriginPC();
|
2016-12-16 01:21:51 +00:00
|
|
|
setCode(editor.getValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
function showLoopTimingForCurrentLine() {
|
|
|
|
var line = getCurrentLine();
|
|
|
|
var pc = line2offset[line];
|
|
|
|
if (pc) {
|
|
|
|
showLoopTimingForPC(pc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2017-01-06 14:49:07 +00:00
|
|
|
function updateDisassembly() {
|
|
|
|
var div = $("#disassembly");
|
|
|
|
if (div.is(':visible')) {
|
2017-01-08 15:51:19 +00:00
|
|
|
disasmview.clearGutter("gutter-info");
|
|
|
|
disasmview.clearGutter("gutter-bytes");
|
|
|
|
disasmview.clearGutter("gutter-offset");
|
|
|
|
disasmview.clearGutter("gutter-clock");
|
2017-01-06 14:49:07 +00:00
|
|
|
var state = lastDebugState || platform.saveState();
|
|
|
|
var mem = state.b;
|
|
|
|
var pc = state.c.PC;
|
2017-01-08 15:51:19 +00:00
|
|
|
var gutters = [];
|
|
|
|
var selline = 0;
|
|
|
|
// TODO: not perfect disassembler
|
|
|
|
function disassemble(start, end) {
|
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end > mem.length) end = mem.length;
|
|
|
|
var disasm = new Disassembler6502().disassemble(mem, start, end, pcvisits);
|
|
|
|
var s = "";
|
|
|
|
for (a in disasm) {
|
|
|
|
var srclinenum = offset2line[a];
|
|
|
|
if (srclinenum) {
|
|
|
|
var srcline = editor.getLine(srclinenum-1);
|
|
|
|
if (srcline && srcline.trim().length) {
|
|
|
|
s += "; " + srclinenum + ":\t" + srcline + "\n";
|
|
|
|
gutters.push([a]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var dline = hex(parseInt(a)) + "\t" + disasm[a] + "\n";
|
|
|
|
s += dline;
|
|
|
|
if (a == pc) selline = gutters.length;
|
|
|
|
gutters.push([]);
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
|
|
|
|
disasmview.setValue(text);
|
|
|
|
/*
|
|
|
|
for (var i=0; i<gutters.length; i++) {
|
|
|
|
var g = gutters[i];
|
|
|
|
if (g[0]) disasmview.setGutterMarker(i, "gutter-offset", hex(g[0]));
|
2017-01-06 14:49:07 +00:00
|
|
|
}
|
2017-01-08 15:51:19 +00:00
|
|
|
*/
|
|
|
|
disasmview.setCursor(selline, 0);
|
|
|
|
// TODO: need to refresh when viewport changes
|
|
|
|
var scrinfo = disasmview.getScrollInfo();
|
|
|
|
disasmview.scrollTo(0, (scrinfo.height-scrinfo.clientHeight)/2);
|
2017-01-06 14:49:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleDisassembly() {
|
|
|
|
$("#disassembly").toggle();
|
|
|
|
$("#editor").toggle();
|
|
|
|
updateDisassembly();
|
|
|
|
}
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
function resetAndDebug() {
|
2017-01-06 16:57:28 +00:00
|
|
|
clearBreakpoint();
|
2017-01-03 01:42:15 +00:00
|
|
|
platform.reset();
|
2016-12-16 01:21:51 +00:00
|
|
|
runToCursor();
|
2017-01-03 01:42:15 +00:00
|
|
|
}
|
2017-01-06 16:57:28 +00:00
|
|
|
|
2017-01-07 18:05:02 +00:00
|
|
|
function _breakExpression() {
|
|
|
|
var exprs = window.prompt("Enter break expression", "c.PC == 0x6000");
|
|
|
|
if (exprs) {
|
|
|
|
var fn = new Function('c', 'return (' + exprs + ');');
|
|
|
|
setupBreakpoint();
|
|
|
|
platform.runEval(fn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
function setupDebugControls(){
|
|
|
|
$("#dbg_reset").click(resetAndDebug);
|
|
|
|
$("#dbg_pause").click(pause);
|
|
|
|
$("#dbg_go").click(resume);
|
|
|
|
$("#dbg_step").click(singleStep);
|
|
|
|
$("#dbg_toline").click(runToCursor);
|
2017-01-07 18:05:02 +00:00
|
|
|
$("#dbg_stepout").click(runUntilReturn);
|
|
|
|
$("#dbg_stepback").click(runStepBackwards);
|
2016-12-16 01:21:51 +00:00
|
|
|
$("#dbg_timing").click(traceTiming);
|
2017-01-06 14:49:07 +00:00
|
|
|
$("#dbg_disasm").click(toggleDisassembly);
|
|
|
|
$("#disassembly").hide();
|
2016-12-30 23:51:15 +00:00
|
|
|
$(".dropdown-menu").collapse({toggle: false});
|
|
|
|
$("#item_new_file").click(_createNewFile);
|
|
|
|
$("#item_share_file").click(_shareFile);
|
|
|
|
$("#item_reset_file").click(_resetPreset);
|
2017-01-07 18:05:02 +00:00
|
|
|
$("#item_debug_expr").click(_breakExpression);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
|
2016-12-18 20:59:31 +00:00
|
|
|
function showWelcomeMessage() {
|
|
|
|
if (!localStorage.getItem("8bitworkshop.hello"))
|
|
|
|
{
|
2016-12-30 23:51:15 +00:00
|
|
|
// Instance the tour
|
|
|
|
var tour = new Tour({
|
2017-01-03 15:43:40 +00:00
|
|
|
autoscroll:false,
|
2016-12-30 23:51:15 +00:00
|
|
|
//storage:false,
|
|
|
|
steps: [
|
|
|
|
{
|
|
|
|
element: "#editor",
|
|
|
|
title: "Welcome to 8bitworkshop!",
|
|
|
|
content: "Type your 6502 code on the left side, and it'll be assembled in real-time. All changes are saved to browser local storage.",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
element: "#emulator",
|
|
|
|
placement: 'left',
|
|
|
|
title: "Atari VCS Emulator",
|
|
|
|
content: "This is an emulator for the Atari VCS/2600. We'll load your assembled code into the emulator whenever you make changes.",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
element: "#preset_select",
|
|
|
|
title: "File Selector",
|
|
|
|
content: "Pick a code example from the book, or access your own files and files shared by others."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
element: "#debug_bar",
|
|
|
|
placement: 'bottom',
|
|
|
|
title: "Debug Tools",
|
|
|
|
content: "Use these buttons to set breakpoints, single step through code, pause/resume, and perform timing analysis."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
element: "#dropdownMenuButton",
|
|
|
|
title: "Main Menu",
|
|
|
|
content: "Click the menu to create new files and share your work with others."
|
|
|
|
},
|
|
|
|
]});
|
|
|
|
tour.init();
|
2017-01-03 15:43:40 +00:00
|
|
|
setTimeout(function() { tour.start(); }, 2000);
|
2016-12-18 20:59:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
///////////////////////////////////////////////////
|
|
|
|
|
|
|
|
var qs = (function (a) {
|
|
|
|
if (!a || a == "")
|
|
|
|
return {};
|
|
|
|
var b = {};
|
|
|
|
for (var i = 0; i < a.length; ++i) {
|
|
|
|
var p = a[i].split('=', 2);
|
|
|
|
if (p.length == 1)
|
|
|
|
b[p[0]] = "";
|
|
|
|
else
|
|
|
|
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
|
|
|
|
}
|
|
|
|
return b;
|
|
|
|
})(window.location.search.substr(1).split('&'));
|
|
|
|
|
2017-01-03 01:42:15 +00:00
|
|
|
// start
|
2016-12-16 01:21:51 +00:00
|
|
|
setupDebugControls();
|
2016-12-30 23:51:15 +00:00
|
|
|
showWelcomeMessage();
|
2017-01-03 01:42:15 +00:00
|
|
|
// parse query string
|
2016-12-16 01:21:51 +00:00
|
|
|
try {
|
|
|
|
// is this a share URL?
|
|
|
|
if (qs['sharekey']) {
|
|
|
|
var sharekey = qs['sharekey'];
|
|
|
|
console.log("Loading shared file ", sharekey);
|
|
|
|
$.getJSON( ".storage/" + sharekey, function( result ) {
|
|
|
|
console.log(result);
|
|
|
|
var newid = 'shared/' + result['filename'];
|
|
|
|
updatePreset(newid, result['text']);
|
|
|
|
qs['file'] = newid;
|
|
|
|
delete qs['sharekey'];
|
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
}, 'text');
|
|
|
|
} else {
|
|
|
|
// add default platform?
|
2017-01-03 20:52:38 +00:00
|
|
|
platform_id = qs['platform'] || localStorage.getItem("__lastplatform");
|
|
|
|
if (!platform_id) {
|
|
|
|
platform_id = qs['platform'] = "vcs";
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
2017-01-03 15:43:40 +00:00
|
|
|
// load and start platform object
|
2017-01-07 02:09:41 +00:00
|
|
|
// TODO: self-register platforms
|
2017-01-03 20:52:38 +00:00
|
|
|
if (platform_id == 'vcs') {
|
2017-01-03 15:43:40 +00:00
|
|
|
platform = new VCSPlatform();
|
2017-01-04 21:07:59 +00:00
|
|
|
$("#booklink_vcs").show();
|
2017-01-03 20:52:38 +00:00
|
|
|
} else if (platform_id == 'apple2') {
|
2017-01-03 15:43:40 +00:00
|
|
|
platform = new Apple2Platform($("#emulator")[0]);
|
2017-01-03 20:52:38 +00:00
|
|
|
} else if (platform_id == 'atarivec') {
|
2017-01-03 15:43:40 +00:00
|
|
|
platform = new AtariVectorPlatform($("#emulator")[0]);
|
2017-01-07 02:09:41 +00:00
|
|
|
} else if (platform_id == 'exidy') {
|
|
|
|
platform = new ExidyPlatform($("#emulator")[0]);
|
2017-01-03 15:43:40 +00:00
|
|
|
} else {
|
2017-01-03 20:52:38 +00:00
|
|
|
alert("Platform " + platform_id + " not recognized");
|
2017-01-03 15:43:40 +00:00
|
|
|
}
|
2017-01-04 21:07:59 +00:00
|
|
|
store = new FileStore(localStorage, platform_id + '/');
|
2017-01-03 15:43:40 +00:00
|
|
|
PRESETS = platform.getPresets();
|
|
|
|
platform.start();
|
2016-12-16 01:21:51 +00:00
|
|
|
// reset file?
|
|
|
|
if (qs['file'] && qs['reset']) {
|
2017-01-04 21:07:59 +00:00
|
|
|
store.deleteFile(qs['file']);
|
2016-12-16 01:21:51 +00:00
|
|
|
qs['reset'] = '';
|
|
|
|
window.location = "?" + $.param(qs);
|
|
|
|
} else if (qs['file']) {
|
|
|
|
// load file
|
|
|
|
loadPreset(qs['file']);
|
|
|
|
updateSelector();
|
|
|
|
} else {
|
|
|
|
// try to load last file
|
2017-01-12 03:28:29 +00:00
|
|
|
var lastid = localStorage.getItem("__lastid_"+platform_id) || localStorage.getItem("__lastid");
|
|
|
|
localStorage.removeItem("__lastid");
|
2016-12-18 20:59:31 +00:00
|
|
|
gotoPresetNamed(lastid || PRESETS[0].id);
|
2016-12-16 01:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
alert(e+""); // TODO?
|
|
|
|
}
|