diff --git a/apple2js.html b/apple2js.html index 581c191..b281c17 100644 --- a/apple2js.html +++ b/apple2js.html @@ -134,7 +134,17 @@
\\ | ').appendTo(row); + for (c = 0; c < 16; c++) { + $('' + toHex(c) + ' | ').appendTo(row); + } + for (r = 0; r < 16; r++) { + row = $('
---|
' + toHex(r * 16) + ' | ').appendTo(row); + for (c = 0; c < 16; c++) { + $('-- | ').appendTo(row).attr('id', 'page' + toHex(_page) + '-' + toHex(r * 16 + c)); + } + } + } + + _init(); + + return { + start: function() { return _page; }, + end: function() { return _page; }, + read: null, + write: function(page, off, val) { + $('#page' + toHex(page) + '-' + toHex(off)).text(toHex(val)); + } + }; +} + var disk_categories = {'Local Saves': []}; var disk_sets = {}; var disk_cur_name = []; @@ -48,8 +91,8 @@ function DriveLights() on ? 'url(css/red-on-16.png)' : 'url(css/red-off-16.png)'); }, - dirty: function(drive, dirty) { - $('#disksave' + drive).button('option', 'disabled', !dirty); + dirty: function() { + // $('#disksave' + drive).button('option', 'disabled', !dirty); }, label: function(drive, label) { if (label) { @@ -77,12 +120,11 @@ function DriveLights() var DISK_TYPES = ['dsk','do','po','raw','nib','2mg']; var TAPE_TYPES = ['wav','aiff','aif','mp3']; -var _saveDrive = 1; -var _loadDrive = 1; +var _currentDrive = 1; function openLoad(drive, event) { - _loadDrive = drive; + _currentDrive = parseInt(drive, 10); if (event.metaKey) { openLoadHTTP(drive); } else { @@ -95,11 +137,18 @@ function openLoad(drive, event) function openSave(drive, event) { - _saveDrive = drive; + _currentDrive = parseInt(drive, 10); + + var mimetype = 'application/octet-stream'; + var data = disk2.getBinary(drive); + var a = $('#local_save_link'); + + var blob = new Blob([data], { 'type': mimetype }); + a.attr('href', window.URL.createObjectURL(blob)); + a.attr('download', drivelights.label(drive) + '.dsk'); + if (event.metaKey) { dumpDisk(drive); - } else if (event.altKey) { - openSaveLocal(drive); } else { $('#save_name').val(drivelights.label(drive)); $('#save').dialog('open'); @@ -108,16 +157,30 @@ function openSave(drive, event) var loading = false; -function loadAjax(url) { +function loadAjax(drive, url) { loading = true; $('#loading').dialog('open'); - $.ajax({ url: url, - cache: false, - dataType: 'jsonp', - jsonp: false, - global: false - }); + $.ajax({ + url: url, + dataType: 'json', + modifiedSince: true, + error: function(xhr, status, error) { + alert(error || status); + $('#loading').dialog('close'); + loading = false; + }, + success: function(data) { + if (data.type == 'binary') { + loadBinary(drive, data); + } else if ($.inArray(data.type, DISK_TYPES) >= 0) { + loadDisk(drive, data); + } + initGamepad(data.gamepad); + $('#loading').dialog('close'); + loading = false; + } + }); } function doLoad() { @@ -132,7 +195,7 @@ function doLoad() { var files = $('#local_file').prop('files'); if (files.length == 1) { - doLoadLocal(); + doLoadLocal(_currentDrive); } else if (url) { var filename; $('#load').dialog('close'); @@ -141,22 +204,26 @@ function doLoad() { if (filename == '__manage') { openManage(); } else { - loadLocalStorage(_loadDrive, filename); + loadLocalStorage(_currentDrive, filename); } } else { var r1 = /json\/disks\/(.*).json$/.exec(url); - if (r1 && _loadDrive == 1) { + if (r1) { filename = r1[1]; - document.location.hash = filename; + } else { + filename = url; } - loadAjax(url); + var parts = document.location.hash.split('|'); + parts[_currentDrive - 1] = filename; + document.location.hash = parts.join('|'); + loadAjax(_currentDrive, url); } } } function doSave() { var name = $('#save_name').val(); - saveLocalStorage(_saveDrive, name); + saveLocalStorage(_currentDrive, name); $('#save').dialog('close'); } @@ -166,14 +233,14 @@ function doDelete(name) { } } -function doLoadLocal() { +function doLoadLocal(drive) { var files = $('#local_file').prop('files'); if (files.length == 1) { var file = files[0]; var parts = file.name.split('.'); var ext = parts[parts.length - 1].toLowerCase(); if ($.inArray(ext, DISK_TYPES) >= 0) { - doLoadLocalDisk(file); + doLoadLocalDisk(drive, file); } else if ($.inArray(ext, TAPE_TYPES) >= 0) { doLoadLocalTape(file); } else { @@ -183,13 +250,13 @@ function doLoadLocal() { } } -function doLoadLocalDisk(file) { +function doLoadLocalDisk(drive, file) { var fileReader = new FileReader(); fileReader.onload = function() { var parts = file.name.split('.'); var name = parts[0], ext = parts[parts.length - 1].toLowerCase(); - if (disk2.setBinary(_saveDrive, name, ext, this.result)) { - drivelights.label(_saveDrive, name); + if (disk2.setBinary(drive, name, ext, this.result)) { + drivelights.label(drive, name); $('#load').dialog('close'); initGamepad(); } @@ -251,7 +318,7 @@ function doLoadLocalTape(file) { fileReader.readAsArrayBuffer(file); } -function doLoadHTTP(_url) { +function doLoadHTTP(drive, _url) { var url = _url || $('#http_url').val(); if (url) { var req = new XMLHttpRequest(); @@ -262,8 +329,8 @@ function doLoadHTTP(_url) { var parts = url.split(/[\/\.]/); var name = decodeURIComponent(parts[parts.length - 2]); var ext = parts[parts.length - 1].toLowerCase(); - if (disk2.setBinary(_saveDrive, name, ext, req.response)) { - drivelights.label(_saveDrive, name); + if (disk2.setBinary(drive, name, ext, req.response)) { + drivelights.label(drive, name); $('#http_load').dialog('close'); initGamepad(); } @@ -273,23 +340,10 @@ function doLoadHTTP(_url) { } function openLoadHTTP(drive) { - _saveDrive = drive; + _currentDrive = parseInt(drive, 10); $('#http_load').dialog('open'); } -function openSaveLocal(drive) { - _saveDrive = drive; - var mimetype = 'application/octet-stream'; - var data = disk2.getBinary(drive); - var a = $('#local_save_link'); - - var blob = new Blob([data], { 'type': mimetype }); - a.attr('href', window.URL.createObjectURL(blob)); - a.attr('download', drivelights.label(drive) + '.dsk'); - - $('#local_save').dialog('open'); -} - function openManage() { $('#manage').dialog('open'); } @@ -307,23 +361,27 @@ var hgr2 = new HiresPage(2); var gr = new LoresPage(1, apple2_charset); var gr2 = new LoresPage(2, apple2_charset); -var rom = new Apple2ROM(); -var dumper = new ApplesoftDump(ram2); +var romVersion = prefs.readPref('computer_type'); +var rom; +if (romVersion == 'apple2') { + rom = new IntBASIC(); +} else if (romVersion == 'original') { + rom = new OriginalROM(); +} else { + rom = new Apple2ROM(); +} + var vm = new VideoModes(gr, hgr, gr2, hgr2); -var mmu = { - auxRom: function(slot, rom) { - cpu.addPageHandler(rom); - } -}; +var dumper = new ApplesoftDump(cpu); var drivelights = new DriveLights(); var io = new Apple2IO(cpu, vm); var keyboard = new KeyBoard(io); -var parallel = new Parallel(io, new Printer(), 1); -var disk2 = new DiskII(io, drivelights, 6); -var slinky = new RAMFactor(mmu, io, 2, 1024 * 1024); -var clock = new Thunderclock(mmu, io, 7); -var lc = new LanguageCard(io, rom); +var lc = new LanguageCard(io, 0, rom); +var parallel = new Parallel(io, 1, new Printer()); +var slinky = new RAMFactor(io, 2, 1024 * 1024); +var disk2 = new DiskII(io, 6, drivelights); +var clock = new Thunderclock(io, 7); cpu.addPageHandler(ram1); cpu.addPageHandler(gr); @@ -332,13 +390,14 @@ cpu.addPageHandler(ram2); cpu.addPageHandler(hgr); cpu.addPageHandler(hgr2); cpu.addPageHandler(ram3); - -cpu.addPageHandler(lc); cpu.addPageHandler(io); -cpu.addPageHandler(parallel); -cpu.addPageHandler(slinky); -cpu.addPageHandler(disk2); -cpu.addPageHandler(clock); +cpu.addPageHandler(lc); + +io.addSlot(0, lc); +io.addSlot(1, parallel); +io.addSlot(2, slinky); +io.addSlot(6, disk2); +io.addSlot(7, clock); var showFPS = false; @@ -403,7 +462,7 @@ function step() var accelerated = false; -function updateSpeed() +function updateCPU() { accelerated = $('#accelerator_toggle').prop('checked'); kHz = accelerated ? 4092 : 1023; @@ -429,6 +488,7 @@ function run(pc) { } var ival = 30; + var now, last = Date.now(); var runFn = function() { now = Date.now(); @@ -447,9 +507,23 @@ function run(pc) { processHash(hash); } } - if (!loading) { - cpu.stepCycles(step); + if (DEBUG) { + cpu.stepCyclesDebug(TRACE ? 1 : step, function() { + var line = cpu.dumpRegisters() + ' ' + + cpu.dumpPC(undefined, SYMBOLS); + if (TRACE) { + debug(line); + } else { + trace.push(line); + if (trace.length > MAX_TRACE) { + trace.shift(); + } + } + }); + } else { + cpu.stepCycles(step); + } vm.blit(); io.sampleTick(); } @@ -479,6 +553,65 @@ function reset() cpu.reset(); } +var state = null; + +function storeStateLocal() { + window.localStorage['apple2.state'] = JSON.stringify(state); +} + +function restoreStateLocal() { + var data = window.localStorage['apple2.state']; + if (data) { + state = JSON.parse(data); + } +} + +function saveState() { + if (state && !window.confirm('Overwrite Saved State?')) { + return; + } + + state = { + cpu: cpu.getState(), + ram1: ram1.getState(), + ram2: ram2.getState(), + ram3: ram3.getState(), + io: io.getState(), + lc: lc.getState(), + vm: vm.getState(), + disk2: disk2.getState(), + drivelights: drivelights.getState() + }; + if (slinky) { + state.slinky = slinky.getState(); + } + + if (window.localStorage) { + storeStateLocal(); + } +} + +function restoreState() { + if (window.localStorage) { + restoreStateLocal(); + } + if (!state) { + return; + } + cpu.setState(state.cpu); + ram1.setState(state.ram1); + ram2.setState(state.ram2); + ram3.setState(state.ram3); + io.setState(state.io); + lc.setState(state.lc); + vm.setState(state.vm); + disk2.setState(state.disk2); + drivelights.setState(state.drivelights); + if (slinky && state.slinky) { + slinky.setState(state.slinky); + } +} + function loadBinary(bin) { stop(); for (var idx = 0; idx < bin.length; idx++) { @@ -499,7 +632,7 @@ function selectCategory() { } var option = $('').val(file.filename).text(name) .appendTo('#disk_select'); - if (disk_cur_name[_loadDrive] == name) { + if (disk_cur_name[_currentDrive] == name) { option.attr('selected', 'selected'); } } @@ -514,7 +647,7 @@ function clickDisk() { doLoad(); } -function loadDisk(disk) { +function loadDisk(drive, disk) { var name = disk.name; var category = disk.category; @@ -522,25 +655,14 @@ function loadDisk(disk) { name += ' - ' + disk.disk; } - disk_cur_cat[_loadDrive] = category; - disk_cur_name[_loadDrive] = name; + disk_cur_cat[drive] = category; + disk_cur_name[drive] = name; - drivelights.label(_loadDrive, name); - disk2.setDisk(_loadDrive, disk); + drivelights.label(drive, name); + disk2.setDisk(drive, disk); initGamepad(disk.gamepad); } -function loadJSON(data) { - if (data.type == 'binary') { - loadBinary(data); - } else if ($.inArray(data.type, DISK_TYPES) >= 0) { - loadDisk(data); - } - initGamepad(data.gamepad); - $('#loading').dialog('close'); - loading = false; -} - /* * LocalStorage Disk Storage */ @@ -608,16 +730,20 @@ function loadLocalStorage(drive, name) { } function processHash(hash) { - if (hash.indexOf('://') > 0) { - var parts = hash.split('.'); - var ext = parts[parts.length - 1].toLowerCase(); - if (ext == 'json') { - loadAjax(hash); + var files = hash.split('|'); + for (var idx = 0; idx < files.length; idx++) { + var file = files[idx]; + if (file.indexOf('://') > 0) { + var parts = file.split('.'); + var ext = parts[parts.length - 1].toLowerCase(); + if (ext == 'json') { + loadAjax(idx + 1, file); + } else { + doLoadHTTP(idx + 1, file); + } } else { - doLoadHTTP(hash); + loadAjax(idx + 1, 'json/disks/' + file + '.json'); } - } else { - loadAjax('json/disks/' + hash + '.json'); } } @@ -626,6 +752,14 @@ function processHash(hash) { */ function _keydown(evt) { + if (!focused || (!evt.metaKey || evt.ctrlKey)) { + evt.preventDefault(); + + var key = keyboard.mapKeyEvent(evt); + if (key != 0xff) { + io.keyDown(key, evt.shiftKey); + } + } if (evt.keyCode === 112) { // F1 - Reset cpu.reset(); } else if (evt.keyCode === 113) { // F2 - Full Screen @@ -647,36 +781,31 @@ function _keydown(evt) { elem.mozRequestFullScreen(); } } + } else if (evt.keyCode === 114) { // F3 + io.keyDown(0x1b); } else if (evt.keyCode == 16) { // Shift keyboard.shiftKey(true); io.buttonDown(2); } else if (evt.keyCode == 17) { // Control keyboard.controlKey(true); - } else if (!focused && (!evt.metaKey || evt.ctrlKey)) { - evt.preventDefault(); - - var key = keyboard.mapKeyEvent(evt); - if (key != 0xff) { - io.keyDown(key); - } } } function _keyup(evt) { + if (!focused) + io.keyUp(); + if (evt.keyCode == 16) { // Shift keyboard.shiftKey(false); io.buttonUp(2); } else if (evt.keyCode == 17) { // Control keyboard.controlKey(false); - } else { - if (!focused) { - io.keyUp(); - } } } function updateScreen() { var green = $('#green_screen').prop('checked'); + scanlines = $('#show_scanlines').prop('checked'); vm.green(green); } @@ -774,21 +903,30 @@ $(function() { keyboard.create($('#keyboard')); if (prefs.havePrefs()) { - $('input[type=checkbox]').each(function() { + $('#options input[type=checkbox]').each(function() { var val = prefs.readPref(this.id); if (val) this.checked = JSON.parse(val); }).change(function() { prefs.writePref(this.id, JSON.stringify(this.checked)); }); + $('#options select').each(function() { + var val = prefs.readPref(this.id); + if (val) + this.value = val; + }).change(function() { + prefs.writePref(this.id, this.value); + }); } - reset(); + if (romVersion != 'original') { + reset(); + } run(); setInterval(updateKHz, 1000); updateSound(); updateScreen(); - updateSpeed(); + updateCPU(); var cancel = function() { $(this).dialog('close'); }; $('#loading').dialog({ autoOpen: false, modal: true }); diff --git a/js/main2e.js b/js/main2e.js index b98287f..6b14c23 100644 --- a/js/main2e.js +++ b/js/main2e.js @@ -1,25 +1,26 @@ -/* globals debug: false, gup: false, hup: false, +/* globals debug: false, gup: false, hup: false, toHex: false CPU6502: false, Apple2eROM: false, Apple2eEnhancedROM: false, apple2e_charset: false, Apple2IO: false - LoresPage: false, HiresPage: false, VideoModes: false + LoresPage: false, HiresPage: false, VideoModes: false, + scanlines: true, KeyBoard: false, Parallel: false, DiskII: false, - RAMFactor: false, Printer: false, MMU: false, Slot3: false, + RAMFactor: false, Thunderclock: false, Prefs: false, disk_index: false, initAudio: false, enableSound: false, initGamepad: false, processGamepad: false, gamepad: false, - ApplesoftDump: false + ApplesoftDump: false, SYMBOLS: false, */ /* exported openLoad, openSave, doDelete, - selectCategory, selectDisk, clickDisk, loadJSON, + selectCategory, selectDisk, clickDisk, updateJoystick, pauseRun, step, restoreState, saveState, @@ -36,6 +37,47 @@ var paused = false; var hashtag; +var DEBUG = false; +var TRACE = false; +var MAX_TRACE = 256; +var trace = []; + +/* + * Page viewer + */ + +function PageDebug(page) +{ + var _page = page; + + function _init() { + var r, c; + var row = $('
\\ | ').appendTo(row); + for (c = 0; c < 16; c++) { + $('' + toHex(c) + ' | ').appendTo(row); + } + for (r = 0; r < 16; r++) { + row = $('
' + toHex(r * 16) + ' | ').appendTo(row); + for (c = 0; c < 16; c++) { + $('-- | ').appendTo(row).attr('id', 'page' + toHex(_page) + '-' + toHex(r * 16 + c)); + } + } + } + + _init(); + + return { + start: function() { return _page; }, + end: function() { return _page; }, + read: null, + write: function(page, off, val) { + $('#page' + toHex(page) + '-' + toHex(off)).text(toHex(val)); + } + }; +} + var disk_categories = {'Local Saves': []}; var disk_sets = {}; var disk_cur_name = []; @@ -49,8 +91,8 @@ function DriveLights() on ? 'url(css/red-on-16.png)' : 'url(css/red-off-16.png)'); }, - dirty: function(drive, dirty) { - $('#disksave' + drive).button('option', 'disabled', !dirty); + dirty: function() { + // $('#disksave' + drive).button('option', 'disabled', !dirty); }, label: function(drive, label) { if (label) { @@ -78,12 +120,11 @@ function DriveLights() var DISK_TYPES = ['dsk','do','po','raw','nib','2mg']; var TAPE_TYPES = ['wav','aiff','aif','mp3']; -var _saveDrive = 1; -var _loadDrive = 1; +var _currentDrive = 1; function openLoad(drive, event) { - _loadDrive = drive; + _currentDrive = parseInt(drive, 10); if (event.metaKey) { openLoadHTTP(drive); } else { @@ -96,11 +137,18 @@ function openLoad(drive, event) function openSave(drive, event) { - _saveDrive = drive; + _currentDrive = parseInt(drive, 10); + + var mimetype = 'application/octet-stream'; + var data = disk2.getBinary(drive); + var a = $('#local_save_link'); + + var blob = new Blob([data], { 'type': mimetype }); + a.attr('href', window.URL.createObjectURL(blob)); + a.attr('download', drivelights.label(drive) + '.dsk'); + if (event.metaKey) { dumpDisk(drive); - } else if (event.altKey) { - openSaveLocal(drive); } else { $('#save_name').val(drivelights.label(drive)); $('#save').dialog('open'); @@ -109,16 +157,30 @@ function openSave(drive, event) var loading = false; -function loadAjax(url) { +function loadAjax(drive, url) { loading = true; $('#loading').dialog('open'); - $.ajax({ url: url, - cache: false, - dataType: 'jsonp', - jsonp: false, - global: false - }); + $.ajax({ + url: url, + dataType: 'json', + modifiedSince: true, + error: function(xhr, status, error) { + alert(error || status); + $('#loading').dialog('close'); + loading = false; + }, + success: function(data) { + if (data.type == 'binary') { + loadBinary(drive, data); + } else if ($.inArray(data.type, DISK_TYPES) >= 0) { + loadDisk(drive, data); + } + initGamepad(data.gamepad); + $('#loading').dialog('close'); + loading = false; + } + }); } function doLoad() { @@ -133,7 +195,7 @@ function doLoad() { var files = $('#local_file').prop('files'); if (files.length == 1) { - doLoadLocal(); + doLoadLocal(_currentDrive); } else if (url) { var filename; $('#load').dialog('close'); @@ -142,22 +204,26 @@ function doLoad() { if (filename == '__manage') { openManage(); } else { - loadLocalStorage(_loadDrive, filename); + loadLocalStorage(_currentDrive, filename); } } else { var r1 = /json\/disks\/(.*).json$/.exec(url); - if (r1 && _loadDrive == 1) { + if (r1) { filename = r1[1]; - document.location.hash = filename; + } else { + filename = url; } - loadAjax(url); + var parts = document.location.hash.split('|'); + parts[_currentDrive - 1] = filename; + document.location.hash = parts.join('|'); + loadAjax(_currentDrive, url); } } } function doSave() { var name = $('#save_name').val(); - saveLocalStorage(_saveDrive, name); + saveLocalStorage(_currentDrive, name); $('#save').dialog('close'); } @@ -167,14 +233,14 @@ function doDelete(name) { } } -function doLoadLocal() { +function doLoadLocal(drive) { var files = $('#local_file').prop('files'); if (files.length == 1) { var file = files[0]; var parts = file.name.split('.'); var ext = parts[parts.length - 1].toLowerCase(); if ($.inArray(ext, DISK_TYPES) >= 0) { - doLoadLocalDisk(file); + doLoadLocalDisk(drive, file); } else if ($.inArray(ext, TAPE_TYPES) >= 0) { doLoadLocalTape(file); } else { @@ -184,13 +250,13 @@ function doLoadLocal() { } } -function doLoadLocalDisk(file) { +function doLoadLocalDisk(drive, file) { var fileReader = new FileReader(); fileReader.onload = function() { var parts = file.name.split('.'); var name = parts[0], ext = parts[parts.length - 1].toLowerCase(); - if (disk2.setBinary(_saveDrive, name, ext, this.result)) { - drivelights.label(_saveDrive, name); + if (disk2.setBinary(drive, name, ext, this.result)) { + drivelights.label(drive, name); $('#load').dialog('close'); initGamepad(); } @@ -252,7 +318,7 @@ function doLoadLocalTape(file) { fileReader.readAsArrayBuffer(file); } -function doLoadHTTP(_url) { +function doLoadHTTP(drive, _url) { var url = _url || $('#http_url').val(); if (url) { var req = new XMLHttpRequest(); @@ -263,8 +329,8 @@ function doLoadHTTP(_url) { var parts = url.split(/[\/\.]/); var name = decodeURIComponent(parts[parts.length - 2]); var ext = parts[parts.length - 1].toLowerCase(); - if (disk2.setBinary(_saveDrive, name, ext, req.response)) { - drivelights.label(_saveDrive, name); + if (disk2.setBinary(drive, name, ext, req.response)) { + drivelights.label(drive, name); $('#http_load').dialog('close'); initGamepad(); } @@ -274,30 +340,18 @@ function doLoadHTTP(_url) { } function openLoadHTTP(drive) { - _saveDrive = drive; + _currentDrive = parseInt(drive, 10); $('#http_load').dialog('open'); } -function openSaveLocal(drive) { - _saveDrive = drive; - var mimetype = 'application/octet-stream'; - var data = disk2.getBinary(drive); - var a = $('#local_save_link'); - - var blob = new Blob([data], { 'type': mimetype }); - a.attr('href', window.URL.createObjectURL(blob)); - a.attr('download', drivelights.label(drive) + '.dsk'); - - $('#local_save').dialog('open'); -} - function openManage() { $('#manage').dialog('open'); } var prefs = new Prefs(); -var enhanced = true; +var enhanced = prefs.readPref('computer_type') != 'apple2e'; var runTimer = null; + var cpu = new CPU6502({'65C02': enhanced}); var hgr = new HiresPage(1); @@ -311,29 +365,30 @@ if (enhanced) { } else { rom = new Apple2eROM(); } + var vm = new VideoModes(gr, hgr, gr2, hgr2); +var dumper = new ApplesoftDump(cpu); var drivelights = new DriveLights(); var io = new Apple2IO(cpu, vm); var keyboard = new KeyBoard(io); var mmu = new MMU(cpu, gr, gr2, hgr, hgr2, io, rom); -var dumper = new ApplesoftDump(mmu); - -var parallel = new Parallel(io, new Printer(), 1); -var disk2 = new DiskII(io, drivelights, 6); -var slot3 = new Slot3(mmu, rom); -var slinky = new RAMFactor(mmu, io, 2, 1024 * 1024); -var clock = new Thunderclock(mmu, io, 7); - -mmu.addSlot(1, parallel); -mmu.addSlot(2, slinky); -mmu.addSlot(3, slot3); -mmu.addSlot(6, disk2); -mmu.addSlot(7, clock); cpu.addPageHandler(mmu); +var parallel = new Parallel(io, 1, new Printer()); +var slinky = new RAMFactor(io, 2, 1024 * 1024); +var slot3 = new Slot3(io, 3, rom); +var disk2 = new DiskII(io, 6, drivelights); +var clock = new Thunderclock(io, 7); + +io.addSlot(1, parallel); +io.addSlot(2, slinky); +io.addSlot(3, slot3); +io.addSlot(6, disk2); +io.addSlot(7, clock); + var showFPS = false; function updateKHz() { @@ -397,7 +452,7 @@ function step() var accelerated = false; -function updateSpeed() +function updateCPU() { accelerated = $('#accelerator_toggle').prop('checked'); kHz = accelerated ? 4092 : 1023; @@ -423,6 +478,7 @@ function run(pc) { } var ival = 30; + var now, last = Date.now(); var runFn = function() { now = Date.now(); @@ -441,10 +497,24 @@ function run(pc) { processHash(hash); } } - if (!loading) { mmu.resetVB(); - cpu.stepCycles(step); + if (DEBUG) { + cpu.stepCyclesDebug(TRACE ? 1 : step, function() { + var line = cpu.dumpRegisters() + ' ' + + cpu.dumpPC(undefined, SYMBOLS); + if (TRACE) { + debug(line); + } else { + trace.push(line); + if (trace.length > MAX_TRACE) { + trace.shift(); + } + } + }); + } else { + cpu.stepCycles(step); + } vm.blit(); io.sampleTick(); } @@ -474,6 +544,59 @@ function reset() cpu.reset(); } +var state = null; + +function storeStateLocal() { + window.localStorage['apple2.state'] = JSON.stringify(state); +} + +function restoreStateLocal() { + var data = window.localStorage['apple2.state']; + if (data) { + state = JSON.parse(data); + } +} + +function saveState() { + if (state && !window.confirm('Overwrite Saved State?')) { + return; + } + + state = { + cpu: cpu.getState(), + io: io.getState(), + mmu: mmu.getState(), + vm: vm.getState(), + disk2: disk2.getState(), + drivelights: drivelights.getState() + }; + if (slinky) { + state.slinky = slinky.getState(); + } + + if (window.localStorage) { + storeStateLocal(); + } +} + +function restoreState() { + if (window.localStorage) { + restoreStateLocal(); + } + if (!state) { + return; + } + cpu.setState(state.cpu); + io.setState(state.io); + mmu.setState(state.mmu); + vm.setState(state.vm); + disk2.setState(state.disk2); + drivelights.setState(state.drivelights); + if (slinky && state.slinky) { + slinky.setState(state.slinky); + } +} + function loadBinary(bin) { stop(); for (var idx = 0; idx < bin.length; idx++) { @@ -494,7 +617,7 @@ function selectCategory() { } var option = $('').val(file.filename).text(name) .appendTo('#disk_select'); - if (disk_cur_name[_loadDrive] == name) { + if (disk_cur_name[_currentDrive] == name) { option.attr('selected', 'selected'); } } @@ -509,7 +632,7 @@ function clickDisk() { doLoad(); } -function loadDisk(disk) { +function loadDisk(drive, disk) { var name = disk.name; var category = disk.category; @@ -517,25 +640,14 @@ function loadDisk(disk) { name += ' - ' + disk.disk; } - disk_cur_cat[_loadDrive] = category; - disk_cur_name[_loadDrive] = name; + disk_cur_cat[drive] = category; + disk_cur_name[drive] = name; - drivelights.label(_loadDrive, name); - disk2.setDisk(_loadDrive, disk); + drivelights.label(drive, name); + disk2.setDisk(drive, disk); initGamepad(disk.gamepad); } -function loadJSON(data) { - if (data.type == 'binary') { - loadBinary(data); - } else if ($.inArray(data.type, DISK_TYPES) >= 0) { - loadDisk(data); - } - initGamepad(data.gamepad); - $('#loading').dialog('close'); - loading = false; -} - /* * LocalStorage Disk Storage */ @@ -603,16 +715,20 @@ function loadLocalStorage(drive, name) { } function processHash(hash) { - if (hash.indexOf('://') > 0) { - var parts = hash.split('.'); - var ext = parts[parts.length - 1].toLowerCase(); - if (ext == 'json') { - loadAjax(hash); + var files = hash.split('|'); + for (var idx = 0; idx < files.length; idx++) { + var file = files[idx]; + if (file.indexOf('://') > 0) { + var parts = file.split('.'); + var ext = parts[parts.length - 1].toLowerCase(); + if (ext == 'json') { + loadAjax(idx + 1, file); + } else { + doLoadHTTP(idx + 1, file); + } } else { - doLoadHTTP(hash); + loadAjax(idx + 1, 'json/disks/' + file + '.json'); } - } else { - loadAjax('json/disks/' + hash + '.json'); } } @@ -620,19 +736,16 @@ function processHash(hash) { * Keyboard/Gamepad routines */ -var _key; function _keydown(evt) { if (!focused) { evt.preventDefault(); var key = keyboard.mapKeyEvent(evt); if (key != 0xff) { - if (_key != 0xff) io.keyUp(); io.keyDown(key, evt.shiftKey); - _key = key; } } - if (evt.keyCode === 112) { + if (evt.keyCode === 112) { // F1 - Reset cpu.reset(); } else if (evt.keyCode === 113) { // F2 - Full Screen var elem = document.getElementById('screen'); @@ -653,12 +766,11 @@ function _keydown(evt) { elem.mozRequestFullScreen(); } } - } else if (evt.keyCode === 114) { + } else if (evt.keyCode === 114) { // F3 io.keyDown(0x1b); - _key = 0x1b; } else if (evt.keyCode == 16) { // Shift keyboard.shiftKey(true); - io.buttonDown(2, true); + io.buttonDown(2); } else if (evt.keyCode == 17) { // Control keyboard.controlKey(true); } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command @@ -669,14 +781,12 @@ function _keydown(evt) { } function _keyup(evt) { - _key = 0xff; - if (!focused) io.keyUp(); if (evt.keyCode == 16) { // Shift keyboard.shiftKey(false); - io.buttonDown(2, false); + io.buttonUp(2); } else if (evt.keyCode == 17) { // Control keyboard.controlKey(false); } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command @@ -688,6 +798,7 @@ function _keyup(evt) { function updateScreen() { var green = $('#green_screen').prop('checked'); + scanlines = $('#show_scanlines').prop('checked'); vm.green(green); } @@ -788,13 +899,20 @@ $(function() { keyboard.create($('#keyboard')); if (prefs.havePrefs()) { - $('input[type=checkbox]').each(function() { + $('#options input[type=checkbox]').each(function() { var val = prefs.readPref(this.id); if (val) this.checked = JSON.parse(val); }).change(function() { prefs.writePref(this.id, JSON.stringify(this.checked)); }); + $('#options select').each(function() { + var val = prefs.readPref(this.id); + if (val) + this.value = val; + }).change(function() { + prefs.writePref(this.id, this.value); + }); } reset(); @@ -802,7 +920,7 @@ $(function() { setInterval(updateKHz, 1000); updateSound(); updateScreen(); - updateSpeed(); + updateCPU(); var cancel = function() { $(this).dialog('close'); }; $('#loading').dialog({ autoOpen: false, modal: true }); @@ -823,10 +941,6 @@ $(function() { modal: true, width: 320, buttons: {'Close': cancel }}); - $('#local_save').dialog({ autoOpen: false, - modal: true, - width: 530, - buttons: {'OK': cancel }}); $('#http_load').dialog({ autoOpen: false, modal: true, width: 530, diff --git a/js/mmu.js b/js/mmu.js index 4fd5c59..54d7e6c 100644 --- a/js/mmu.js +++ b/js/mmu.js @@ -21,8 +21,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) var idx; - var _auxRom = 0x00; - var _readPages = new Array(0x100); var _writePages = new Array(0x100); var _pages = new Array(0x100); @@ -47,6 +45,69 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) var _vbEnd = 0; + /* + * I/O Switch locations + */ + + var LOC = { + // 80 Column + _80STOREOFF: 0x00, + _80STOREON: 0x01, + + // Aux RAM + RAMRDOFF: 0x02, + RAMRDON: 0x03, + + RAMWROFF: 0x04, + RAMWRON: 0x05, + + // Bank switched ROM + INTCXROMOFF: 0x06, + INTCXROMON: 0x07, + ALTZPOFF: 0x08, + ALTZPON: 0x09, + SLOTC3ROMOFF: 0x0A, + SLOTC3ROMON: 0x0B, + + // Status + BSRBANK2: 0x11, + BSRREADRAM: 0x12, + RAMRD: 0x13, + RAMWRT: 0x14, + INTCXROM: 0x15, + ALTZP: 0x16, + SLOTC3ROM: 0x17, + _80STORE: 0x18, + VERTBLANK: 0x19, + + PAGE1: 0x54, // select text/graphics page1 main/aux + PAGE2: 0x55, // select text/graphics page2 main/aux + + // Bank 2 + READBSR2: 0x80, + WRITEBSR2: 0x81, + OFFBSR2: 0x82, + READWRBSR2: 0x83, + + // Shadow Bank 2 + _READBSR2: 0x84, + _WRITEBSR2: 0x85, + _OFFBSR2: 0x86, + _READWRBSR2: 0x87, + + // Bank 1 + READBSR1: 0x88, + WRITEBSR1: 0x89, + OFFBSR1: 0x8a, + READWRBSR1: 0x8b, + + // Shadow Bank 1 + _READBSR1: 0x8c, + _WRITEBSR1: 0x8d, + _OFFBSR1: 0x8e, + _READWRBSR1: 0x8f + }; + function _initSwitches() { _bank1 = true; _readbsr = false; @@ -64,28 +125,69 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) } function _debug() { - // debug.apply(arguments); + debug.apply(arguments); } var _last = 0x00; - function EmptySlots() { + function Switches() { + var locs = {}; + + for (var loc in LOC) { + if (LOC.hasOwnProperty(loc)) { + locs[LOC[loc]] = loc; + } + } + return { - start: function slot_start() { - return 0xC1; + start: function() { + return 0xC0; }, - end: function slot_end() { - return 0xCF; + end: function() { + return 0xC0; }, - read: function slot_read() { - return 0x00; + read: function(page, off) { + var result; + if (off in locs) { + result = _access(off); + } else { + result = io.ioSwitch(off); + } + return result; }, - write: function slot_write() { + write: function(page, off, val) { + if (off in locs) { + _access(off, val); + } else { + io.ioSwitch(off, val); + } } }; } - var emptyslots = new EmptySlots(); + function AuxRom() { + return { + start: function() { + return 0xC1; + }, + end: function() { + return 0xCF; + }, + read: function(page, off) { + var result; + if (page == 0xc3) { + result = io.read(page, off); + } else { + result = rom.read(page, off); + } + return result; + }, + write: function() {} + }; + } + + var switches = new Switches(); + var auxRom = new AuxRom(); var mem00_01 = [new RAM(0x0, 0x1), new RAM(0x0, 0x1)]; var mem02_03 = [new RAM(0x2, 0x3), new RAM(0x2, 0x3)]; @@ -95,8 +197,8 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) var mem20_3F = [hires1.bank0(), hires1.bank1()]; var mem40_5F = [hires2.bank0(), hires2.bank1()]; var mem60_BF = [new RAM(0x60,0xBF), new RAM(0x60,0xBF)]; - var memC0_C0 = [io]; - // var memC1_CF = [emptyslots, rom]; + var memC0_C0 = [switches]; + var memC1_CF = [io, auxRom]; var memD0_DF = [rom, new RAM(0xD0,0xDF), new RAM(0xD0,0xDF), new RAM(0xD0,0xDF), new RAM(0xD0,0xDF)]; @@ -161,7 +263,7 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) _writePages[idx] = _pages[idx][0]; // Slots for (idx = 0xc1; idx < 0xd0; idx++) { - _pages[idx] = [emptyslots, rom]; // memC1_CF; + _pages[idx] = memC1_CF; _readPages[idx] = _pages[idx][0]; _writePages[idx] = _pages[idx][0]; } @@ -178,69 +280,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) _writePages[idx] = _pages[idx][0]; } - /* - * I/O Switch locations - */ - - var LOC = { - // 80 Column - _80STOREOFF: 0x00, - _80STOREON: 0x01, - - // Aux RAM - RAMRDOFF: 0x02, - RAMRDON: 0x03, - - RAMWROFF: 0x04, - RAMWRON: 0x05, - - // Bank switched ROM - INTCXROMOFF: 0x06, - INTCXROMON: 0x07, - ALTZPOFF: 0x08, - ALTZPON: 0x09, - SLOTC3ROMOFF: 0x0A, - SLOTC3ROMON: 0x0B, - - // Status - BSRBANK2: 0x11, - BSRREADRAM: 0x12, - RAMRD: 0x13, - RAMWRT: 0x14, - INTCXROM: 0x15, - ALTZP: 0x16, - SLOTC3ROM: 0x17, - _80STORE: 0x18, - VERTBLANK: 0x19, - - PAGE1: 0x54, // select text/graphics page1 main/aux - PAGE2: 0x55, // select text/graphics page2 main/aux - - // Bank 2 - READBSR2: 0x80, - WRITEBSR2: 0x81, - OFFBSR2: 0x82, - READWRBSR2: 0x83, - - // Shadow Bank 2 - _READBSR2: 0x84, - _WRITEBSR2: 0x85, - _OFFBSR2: 0x86, - _READWRBSR2: 0x87, - - // Bank 1 - READBSR1: 0x88, - WRITEBSR1: 0x89, - OFFBSR1: 0x8a, - READWRBSR1: 0x8b, - - // Shadow Bank 1 - _READBSR1: 0x8c, - _WRITEBSR1: 0x8d, - _OFFBSR1: 0x8e, - _READWRBSR1: 0x8f - }; - function _updateBanks() { if (_auxRamRead) { for (idx = 0x02; idx < 0xC0; idx++) { @@ -549,7 +588,7 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) case LOC._80STORE: // 0xC018 result = _80store ? 0x80 : 0x00; break; - case LOC.VERTBLANK: // 0xC018 + case LOC.VERTBLANK: // 0xC019 // result = cpu.cycles() % 20 < 5 ? 0x80 : 0x00; result = (cpu.cycles() < _vbEnd) ? 0x80 : 0x00; break; @@ -577,17 +616,11 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) lores1.start(); lores2.start(); - // Do us afterward because we override some of the above - io.registerSwitches(this, LOC); - return 0x00; }, end: function mmu_end() { return 0xff; }, - ioSwitch: function mmu_ioswitch(off, val) { - return _access(off, val); - }, reset: function() { _initSwitches(); _updateBanks(); @@ -603,24 +636,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) write: function mmu_write(page, off, val) { _writePages[page].write(page, off, val); }, - addSlot: function mmu_addSlot(slot, card) { - if (slot == 3) { - _pages[0xc0 + slot][1] = card; - } - _pages[0xc0 + slot][0] = card; - card.start(); - }, - auxRom: function mmu_auxRom(slot, rom) { - var idx; - if (_auxRom != slot) { - _debug("Slot " + slot + " expansion rom added"); - _auxRom = slot; - for (idx = 0xc8; idx < 0xd0; idx++) { - _pages[idx][0] = rom; - } - _updateBanks(); - } - }, resetVB: function mmu_resetVB() { _vbEnd = cpu.cycles() + 1000; }, @@ -631,7 +646,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) bank1: _bank1, last: _last, - auxRom: _auxRom, intcxrom: _intcxrom, slot3rom: _slot3rom, auxRamRead: _auxRamRead, @@ -655,7 +669,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom) _writebsr = state.writebsr; _bank1 = state.bank1; - _auxRom = state.auxRom; _intcxrom = state.intcxrom; _slot3rom = state.slot3rom; _auxRamRead = state.auxRamRead; diff --git a/js/parallel.js b/js/parallel.js index 6472bb4..6f9aebb 100644 --- a/js/parallel.js +++ b/js/parallel.js @@ -10,18 +10,19 @@ */ /*exported Parallel */ +/*globals debug: false */ -function Parallel(io, cbs, slot) { +function Parallel(io, slot, cbs) { 'use strict'; slot = slot || 1; + debug('Parallel card in slot', slot); + var LOC = { IOREG: 0x80 }; - var _cbs = cbs; - var rom = [ 0x18,0xb0,0x38,0x48,0x8a,0x48,0x98,0x48, 0x08,0x78,0x20,0x58,0xff,0xba,0x68,0x68, @@ -57,18 +58,23 @@ function Parallel(io, cbs, slot) { 0xff,0xf0,0x03,0xfe,0x38,0x07,0x70,0x84 ]; + LOC.IOREG += 0x10 * slot; + + function _access(off, val) { + if (off == LOC.IOREG && val && 'putChar' in cbs) { + cbs.putChar(val); + } + } + return { start: function() { - LOC.IOREG += 0x10 * slot; - io.registerSwitches(this, LOC); return 0xc0 + slot; }, end: function() { return 0xc0 + slot; }, - ioSwitch: function(off, val) { - if (off == LOC.IOREG && val && 'putChar' in _cbs) - _cbs.putChar(val); + ioSwitch: function (off, val) { + return _access(off, val); }, read: function(page, off) { return rom[off]; diff --git a/js/ramfactor.js b/js/ramfactor.js index 379a210..439f6b6 100644 --- a/js/ramfactor.js +++ b/js/ramfactor.js @@ -9,11 +9,11 @@ * implied warranty. */ -/*exported RAMFactor*/ -/*globals allocMem: false, bytify: false, each: false, +/*exported RAMFactor */ +/*globals allocMem: false, bytify: false, debug: false, each: false, base64_encode: false, base64_decode: false */ -function RAMFactor(mmu, io, slot, size) { +function RAMFactor(io, slot, size) { 'use strict'; var rom = [ @@ -1066,6 +1066,8 @@ function RAMFactor(mmu, io, slot, size) { }; function _init() { + debug('RAMFactor card in slot', slot); + each(LOC, function(key) { LOC[key] += slot * 0x10; }); @@ -1154,19 +1156,6 @@ function RAMFactor(mmu, io, slot, size) { return result; } - var auxRomFn = { - start: function auxRom_start() { - return 0xc8; - }, - end: function auxRom_end() { - return 0xcf; - }, - read: function auxRom_read(page, off) { - return rom[_firmware * 0x1000 + (page - 0xC0) * 0x100 + off]; - }, - write: function auxRom_write() {} - }; - _init(); return { @@ -1177,17 +1166,19 @@ function RAMFactor(mmu, io, slot, size) { end: function ramfactor_end() { return 0xc0 + slot; }, - read: function ramfactor_read(page, off) { - mmu.auxRom(slot, auxRomFn); - - return rom[slot * 0x100 + off]; - }, - write: function ramfactor_write() { - mmu.auxRom(slot, auxRomFn); - }, - ioSwitch: function ramfactor_ioSwitch(off, val) { + ioSwitch: function (off, val) { return _access(off, val); }, + read: function ramfactor_read(page, off) { + var result; + if (page == 0xc0 + slot) { + result = rom[slot * 0x100 + off]; + } else { + result = rom[_firmware * 0x1000 + (page - 0xC0) * 0x100 + off]; + } + return result; + }, + write: function ramfactor_write() {}, reset: function ramfactor_reset() { _firmware = 0; }, diff --git a/js/slot3.js b/js/slot3.js index c204fcb..6e43854 100644 --- a/js/slot3.js +++ b/js/slot3.js @@ -9,25 +9,12 @@ * implied warranty. */ -/*exported Slot3*/ +/*exported Slot3 */ -function Slot3(mmu, rom) +function Slot3(io, slot, rom) { 'use strict'; - var auxRomFn = { - start: function auxRom_start() { - return 0xc8; - }, - end: function auxRom_end() { - return 0xcf; - }, - read: function auxRom_read(page, off) { - return rom.read(page, off); - }, - write: function auxRom_write() {} - }; - return { start: function slot3_start() { return 0xc3; @@ -36,11 +23,9 @@ function Slot3(mmu, rom) return 0xc3; }, read: function slot3_read(page, off) { - mmu.auxRom(0x3, auxRomFn); return rom.read(page, off); }, write: function slot3_write() { - mmu.auxRom(0x3, auxRomFn); } }; diff --git a/js/thunderclock.js b/js/thunderclock.js index 0164109..eb0347d 100644 --- a/js/thunderclock.js +++ b/js/thunderclock.js @@ -10,9 +10,9 @@ */ /*exported Thunderclock */ -/*global each: false */ +/*global debug: false, each: false */ -function Thunderclock(mmu, io, slot) +function Thunderclock(io, slot) { 'use strict'; @@ -287,24 +287,13 @@ function Thunderclock(mmu, io, slot) }; function _init() { + debug('Thunderclock card in slot', slot); + each(LOC, function(key) { LOC[key] += slot * 0x10; }); } - var auxRomFn = { - start: function auxRom_start() { - return 0xc8; - }, - end: function auxRom_end() { - return 0xcf; - }, - read: function auxRom_read(page, off) { - return rom[(page - 0xc8) * 256 + off]; - }, - write: function auxRom_write() {} - }; - var _command = 0; var _bits = []; @@ -372,19 +361,21 @@ function Thunderclock(mmu, io, slot) return { start: function thunderclock_start() { - io.registerSwitches(this, LOC); return 0xc0 + slot; }, end: function thunderclock_end() { return 0xc0 + slot; }, read: function thunderclock_read(page, off) { - mmu.auxRom(slot, auxRomFn); - - return rom[off]; + var result; + if (page < 0xc8) { + result = rom[off]; + } else { + result = rom[(page - 0xc8) * 256 + off]; + } + return result; }, write: function thunderclock_write() { - mmu.auxRom(slot, auxRomFn); }, ioSwitch: function thunderclock_ioSwitch(off, val) { return _access(off, val); diff --git a/js/ui/audio.js b/js/ui/audio.js index 4a96957..306c67a 100644 --- a/js/ui/audio.js +++ b/js/ui/audio.js @@ -21,10 +21,9 @@ var _samples = []; var audioContext; var audioNode; -var AC = window.AudioContext; -if (typeof AC !== 'undefined') { - audioContext = new AC(); +if (window.AudioContext) { + audioContext = new window.AudioContext(); audioNode = audioContext.createScriptProcessor(4096, 1, 1); audioNode.onaudioprocess = function(event) { diff --git a/js/ui/gamepad.js b/js/ui/gamepad.js index 450af5f..4ab2219 100644 --- a/js/ui/gamepad.js +++ b/js/ui/gamepad.js @@ -1,4 +1,3 @@ - /* Copyright 2010-2016 Will Scullin