mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
More typescript conversion (#46)
* Convert js/ram to a class * Convert js/mmu to Typescript * Convert js/apple2io to Typescript * Convert js/canvas to Typescript * Use new types in js/mmu * Rename js/symbols.js to js/symbols.ts * Remove the difference between readPages and writePages As @whscullin said in PR #38, there's no need to have both readable and writable pages since all implementations are currently both. This change combines them into `Page`. Likewise, `PageHandler` now extends `Page`. `Apple2IO` now implements `PageHandler`. This caught a bug where `end` had been renamed `endend` by mistake. There are a few other formatting changes as well. * Convert js/apple2 to Typescript * Convert js/prefs to Typescript * Convert all of the ROMs in js/roms to Typescript Now all of the ROMs are classes that extend the ROM class. There is some rudamentary checking to make sure that the length of the ROM matches the declared start and end pages. (This caught what looks to be an error in roms/apple2e, but it's hard for me to tell.) The typing also caught an error where the character ROM was being used for the main ROM for the apple2j version. * Convert js/roms/cards/* to Typescript * Convert js/formats/format_utils to Typescript This change also seems to fix a bug with `.po` image files that weren't being read correctly.
This commit is contained in:
parent
e1b807ba9e
commit
b80436d99c
199
js/apple2.js
199
js/apple2.js
@ -1,199 +0,0 @@
|
||||
import Apple2IO from './apple2io';
|
||||
import { HiresPage, LoresPage, VideoModes } from './canvas';
|
||||
import CPU6502 from './cpu6502';
|
||||
import MMU from './mmu';
|
||||
import RAM from './ram';
|
||||
import { debug } from './util';
|
||||
|
||||
import SYMBOLS from './symbols';
|
||||
|
||||
export function Apple2(options) {
|
||||
var stats = {
|
||||
frames: 0,
|
||||
renderedFrames: 0
|
||||
};
|
||||
|
||||
var paused = false;
|
||||
|
||||
var DEBUG = false;
|
||||
var TRACE = false;
|
||||
var MAX_TRACE = 256;
|
||||
var trace = [];
|
||||
|
||||
var runTimer = null;
|
||||
var runAnimationFrame = null;
|
||||
var cpu = new CPU6502({ '65C02': options.enhanced });
|
||||
|
||||
var gr = new LoresPage(1, options.characterRom, options.e, options.screen[0]);
|
||||
var gr2 = new LoresPage(2, options.characterRom, options.e, options.screen[1]);
|
||||
var hgr = new HiresPage(1, options.screen[2]);
|
||||
var hgr2 = new HiresPage(2, options.screen[3]);
|
||||
|
||||
var vm = new VideoModes(gr, hgr, gr2, hgr2, options.e);
|
||||
vm.multiScreen(options.multiScreen);
|
||||
vm.enhanced(options.enhanced);
|
||||
|
||||
var io = new Apple2IO(cpu, vm);
|
||||
var mmu = null;
|
||||
|
||||
if (options.e) {
|
||||
mmu = new MMU(cpu, vm, gr, gr2, hgr, hgr2, io, options.rom);
|
||||
cpu.addPageHandler(mmu);
|
||||
} else {
|
||||
var ram1 = new RAM(0x00, 0x03),
|
||||
ram2 = new RAM(0x0C, 0x1F),
|
||||
ram3 = new RAM(0x60, 0xBF);
|
||||
|
||||
cpu.addPageHandler(ram1);
|
||||
cpu.addPageHandler(gr);
|
||||
cpu.addPageHandler(gr2);
|
||||
cpu.addPageHandler(ram2);
|
||||
cpu.addPageHandler(hgr);
|
||||
cpu.addPageHandler(hgr2);
|
||||
cpu.addPageHandler(ram3);
|
||||
cpu.addPageHandler(io);
|
||||
cpu.addPageHandler(options.rom);
|
||||
}
|
||||
|
||||
var _requestAnimationFrame =
|
||||
window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame;
|
||||
|
||||
var _cancelAnimationFrame =
|
||||
window.cancelAnimationFrame ||
|
||||
window.mozCancelAnimationFrame ||
|
||||
window.webkitCancelAnimationFrame ||
|
||||
window.msCancelAnimationFrame;
|
||||
|
||||
/**
|
||||
* Runs the emulator. If the emulator is already running, this does
|
||||
* nothing. When this function exits either `runTimer` or
|
||||
* `runAnimationFrame` will be non-null.
|
||||
*/
|
||||
function run() {
|
||||
if (runTimer || runAnimationFrame) {
|
||||
return; // already running
|
||||
}
|
||||
|
||||
var interval = 30;
|
||||
|
||||
var now, last = Date.now();
|
||||
var runFn = function() {
|
||||
var kHz = io.getKHz();
|
||||
now = Date.now();
|
||||
|
||||
var step = (now - last) * kHz, stepMax = kHz * interval;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (mmu) {
|
||||
mmu.resetVB();
|
||||
}
|
||||
if (io.annunciator(0)) {
|
||||
if (options.multiScreen) {
|
||||
vm.blit();
|
||||
}
|
||||
if (io.blit()) {
|
||||
stats.renderedFrames++;
|
||||
}
|
||||
} else {
|
||||
if (vm.blit()) {
|
||||
stats.renderedFrames++;
|
||||
}
|
||||
}
|
||||
stats.frames++;
|
||||
io.tick();
|
||||
options.tick();
|
||||
|
||||
if (!paused && _requestAnimationFrame) {
|
||||
runAnimationFrame = _requestAnimationFrame(runFn);
|
||||
}
|
||||
};
|
||||
if (_requestAnimationFrame) {
|
||||
runAnimationFrame = _requestAnimationFrame(runFn);
|
||||
} else {
|
||||
runTimer = setInterval(runFn, interval);
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (runTimer) {
|
||||
clearInterval(runTimer);
|
||||
}
|
||||
if (runAnimationFrame) {
|
||||
_cancelAnimationFrame(runAnimationFrame);
|
||||
}
|
||||
runTimer = null;
|
||||
runAnimationFrame = null;
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
var state = {
|
||||
cpu: cpu.getState(),
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function restoreState(state) {
|
||||
cpu.setState(state.cpu);
|
||||
}
|
||||
|
||||
return {
|
||||
reset: function () {
|
||||
cpu.reset();
|
||||
},
|
||||
|
||||
run: function () {
|
||||
run();
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
stop();
|
||||
},
|
||||
|
||||
saveState: function () {
|
||||
saveState();
|
||||
},
|
||||
|
||||
restoreState: function () {
|
||||
restoreState();
|
||||
},
|
||||
|
||||
getStats: function () {
|
||||
return stats;
|
||||
},
|
||||
|
||||
getCPU: function () {
|
||||
return cpu;
|
||||
},
|
||||
|
||||
getIO: function () {
|
||||
return io;
|
||||
},
|
||||
|
||||
getVideoModes: function () {
|
||||
return vm;
|
||||
}
|
||||
};
|
||||
}
|
198
js/apple2.ts
Normal file
198
js/apple2.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import Apple2IO from './apple2io';
|
||||
import { HiresPage, LoresPage, VideoModes } from './canvas';
|
||||
import CPU6502, { PageHandler, CpuState } from './cpu6502';
|
||||
import MMU from './mmu';
|
||||
import RAM from './ram';
|
||||
import { debug } from './util';
|
||||
|
||||
import SYMBOLS from './symbols';
|
||||
import { Restorable } from './types';
|
||||
|
||||
interface Options {
|
||||
characterRom: any,
|
||||
enhanced: boolean,
|
||||
e: boolean,
|
||||
multiScreen: boolean,
|
||||
rom: PageHandler,
|
||||
screen: any[],
|
||||
tick: () => void,
|
||||
}
|
||||
|
||||
interface State {
|
||||
cpu: CpuState,
|
||||
}
|
||||
|
||||
export class Apple2 implements Restorable<State> {
|
||||
private paused = false;
|
||||
|
||||
private DEBUG = false;
|
||||
private TRACE = false;
|
||||
private MAX_TRACE = 256;
|
||||
private trace: string[] = [];
|
||||
|
||||
private runTimer: number | null = null;
|
||||
private runAnimationFrame: number | null = null;
|
||||
private cpu: CPU6502;
|
||||
|
||||
private gr: LoresPage;
|
||||
private gr2: LoresPage;
|
||||
private hgr: HiresPage;
|
||||
private hgr2: HiresPage;
|
||||
private vm: VideoModes;
|
||||
|
||||
private io: Apple2IO;
|
||||
private mmu: MMU;
|
||||
|
||||
private multiScreen: boolean;
|
||||
private tick: () => void;
|
||||
|
||||
private stats = {
|
||||
frames: 0,
|
||||
renderedFrames: 0
|
||||
};
|
||||
|
||||
constructor(options: Options) {
|
||||
this.cpu = new CPU6502({ '65C02': options.enhanced })
|
||||
this.gr = new LoresPage(1, options.characterRom, options.e, options.screen[0]);
|
||||
this.gr2 = new LoresPage(2, options.characterRom, options.e, options.screen[1]);
|
||||
this.hgr = new HiresPage(1, options.screen[2]);
|
||||
this.hgr2 = new HiresPage(2, options.screen[3]);
|
||||
this.vm = new VideoModes(this.gr, this.hgr, this.gr2, this.hgr2, options.e);
|
||||
this.vm.multiScreen(options.multiScreen);
|
||||
this.vm.enhanced(options.enhanced);
|
||||
this.io = new Apple2IO(this.cpu, this.vm);
|
||||
this.multiScreen = options.multiScreen;
|
||||
this.tick = options.tick;
|
||||
|
||||
if (options.e) {
|
||||
this.mmu = new MMU(this.cpu, this.vm, this.gr, this.gr2, this.hgr, this.hgr2, this.io, options.rom);
|
||||
this.cpu.addPageHandler(this.mmu);
|
||||
} else {
|
||||
let ram1 = new RAM(0x00, 0x03);
|
||||
let ram2 = new RAM(0x0C, 0x1F);
|
||||
let ram3 = new RAM(0x60, 0xBF);
|
||||
|
||||
this.cpu.addPageHandler(ram1);
|
||||
this.cpu.addPageHandler(this.gr);
|
||||
this.cpu.addPageHandler(this.gr2);
|
||||
this.cpu.addPageHandler(ram2);
|
||||
this.cpu.addPageHandler(this.hgr);
|
||||
this.cpu.addPageHandler(this.hgr2);
|
||||
this.cpu.addPageHandler(ram3);
|
||||
this.cpu.addPageHandler(this.io);
|
||||
this.cpu.addPageHandler(options.rom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the emulator. If the emulator is already running, this does
|
||||
* nothing. When this function exits either `runTimer` or
|
||||
* `runAnimationFrame` will be non-null.
|
||||
*/
|
||||
run() {
|
||||
if (this.runTimer || this.runAnimationFrame) {
|
||||
return; // already running
|
||||
}
|
||||
|
||||
let interval = 30;
|
||||
|
||||
let now, last = Date.now();
|
||||
let runFn = () => {
|
||||
let kHz = this.io.getKHz();
|
||||
now = Date.now();
|
||||
|
||||
let step = (now - last) * kHz, stepMax = kHz * interval;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
if (this.DEBUG) {
|
||||
this.cpu.stepCyclesDebug(this.TRACE ? 1 : step, () => {
|
||||
let line = this.cpu.dumpRegisters() + ' ' +
|
||||
this.cpu.dumpPC(undefined, SYMBOLS);
|
||||
if (this.TRACE) {
|
||||
debug(line);
|
||||
} else {
|
||||
this.trace.push(line);
|
||||
if (this.trace.length > this.MAX_TRACE) {
|
||||
this.trace.shift();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.cpu.stepCycles(step);
|
||||
}
|
||||
if (this.mmu) {
|
||||
this.mmu.resetVB();
|
||||
}
|
||||
if (this.io.annunciator(0)) {
|
||||
if (this.multiScreen) {
|
||||
this.vm.blit();
|
||||
}
|
||||
if (this.io.blit()) {
|
||||
this.stats.renderedFrames++;
|
||||
}
|
||||
} else {
|
||||
if (this.vm.blit()) {
|
||||
this.stats.renderedFrames++;
|
||||
}
|
||||
}
|
||||
this.stats.frames++;
|
||||
this.io.tick();
|
||||
this.tick();
|
||||
|
||||
if (!this.paused && requestAnimationFrame) {
|
||||
this.runAnimationFrame = requestAnimationFrame(runFn);
|
||||
}
|
||||
};
|
||||
if (requestAnimationFrame) {
|
||||
this.runAnimationFrame = requestAnimationFrame(runFn);
|
||||
} else {
|
||||
this.runTimer = window.setInterval(runFn, interval);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.runTimer) {
|
||||
clearInterval(this.runTimer);
|
||||
}
|
||||
if (this.runAnimationFrame) {
|
||||
cancelAnimationFrame(this.runAnimationFrame);
|
||||
}
|
||||
this.runTimer = null;
|
||||
this.runAnimationFrame = null;
|
||||
}
|
||||
|
||||
getState(): State {
|
||||
let state: State = {
|
||||
cpu: this.cpu.getState(),
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
setState(state: State) {
|
||||
this.cpu.setState(state.cpu);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return this.stats;
|
||||
}
|
||||
|
||||
getCPU() {
|
||||
return this.cpu;
|
||||
}
|
||||
|
||||
getIO() {
|
||||
return this.io;
|
||||
}
|
||||
|
||||
getVideoModes() {
|
||||
return this.vm;
|
||||
}
|
||||
}
|
469
js/apple2io.js
469
js/apple2io.js
@ -1,469 +0,0 @@
|
||||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { debug } from './util';
|
||||
|
||||
export default function Apple2IO(cpu, vm)
|
||||
{
|
||||
var _slot = [];
|
||||
var _auxRom = null;
|
||||
|
||||
var _khz = 1023;
|
||||
var _rate = 44000;
|
||||
var _sample_size = 4096;
|
||||
|
||||
var _cycles_per_sample;
|
||||
|
||||
var _buffer = [];
|
||||
var _key = 0;
|
||||
var _keyDown = false;
|
||||
var _button = [false, false, false];
|
||||
var _paddle = [0.0, 0.0, 0.0, 0,0];
|
||||
var _phase = -1;
|
||||
var _sample = [];
|
||||
var _sampleIdx = 0;
|
||||
var _sampleTime = 0;
|
||||
var _didAudio = false;
|
||||
|
||||
var _high = 0.5;
|
||||
var _low = -0.5;
|
||||
|
||||
var _audioListener = null;
|
||||
|
||||
var _trigger = 0;
|
||||
var _annunciators = [false, false, false, false];
|
||||
|
||||
var _tape = [];
|
||||
var _tapeOffset = 0;
|
||||
var _tapeNext = 0;
|
||||
var _tapeCurrent = false;
|
||||
|
||||
var LOC = {
|
||||
KEYBOARD: 0x00, // keyboard data (latched) (Read),
|
||||
STROBE: 0x10, // clear bit 7 of keyboard data ($C000)
|
||||
TAPEOUT: 0x20, // toggle the cassette output.
|
||||
SPEAKER: 0x30, // toggle speaker diaphragm
|
||||
C040STB: 0x40, // trigger game port sync
|
||||
CLRTEXT: 0x50, // display graphics
|
||||
SETTEXT: 0x51, // display text
|
||||
CLRMIXED: 0x52, // clear mixed mode- enable full graphics
|
||||
SETMIXED: 0x53, // enable graphics/text mixed mode
|
||||
PAGE1: 0x54, // select text/graphics page1
|
||||
PAGE2: 0x55, // select text/graphics page2
|
||||
CLRHIRES: 0x56, // select Lo-res
|
||||
SETHIRES: 0x57, // select Hi-res
|
||||
CLRAN0: 0x58, // Set annunciator-0 output to 0
|
||||
SETAN0: 0x59, // Set annunciator-0 output to 1
|
||||
CLRAN1: 0x5A, // Set annunciator-1 output to 0
|
||||
SETAN1: 0x5B, // Set annunciator-1 output to 1
|
||||
CLRAN2: 0x5C, // Set annunciator-2 output to 0
|
||||
SETAN2: 0x5D, // Set annunciator-2 output to 1
|
||||
CLRAN3: 0x5E, // Set annunciator-3 output to 0
|
||||
SETAN3: 0x5F, // Set annunciator-3 output to 1
|
||||
TAPEIN: 0x60, // bit 7: data from cassette
|
||||
PB0: 0x61, // game Pushbutton 0 / open apple (command) key data
|
||||
PB1: 0x62, // game Pushbutton 1 / closed apple (option) key data
|
||||
PB2: 0x63, // game Pushbutton 2 (read)
|
||||
PADDLE0: 0x64, // bit 7: status of pdl-0 timer (read)
|
||||
PADDLE1: 0x65, // bit 7: status of pdl-1 timer (read)
|
||||
PADDLE2: 0x66, // bit 7: status of pdl-2 timer (read)
|
||||
PADDLE3: 0x67, // bit 7: status of pdl-3 timer (read)
|
||||
PDLTRIG: 0x70, // trigger paddles
|
||||
ACCEL: 0x74, // CPU Speed control
|
||||
};
|
||||
|
||||
function init() {
|
||||
_calcSampleRate();
|
||||
}
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _tick() {
|
||||
var now = cpu.getCycles();
|
||||
var phase = _didAudio ? (_phase > 0 ? _high : _low) : 0.0;
|
||||
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
|
||||
_sample[_sampleIdx++] = phase;
|
||||
if (_sampleIdx === _sample_size) {
|
||||
if (_audioListener) {
|
||||
_audioListener(_sample);
|
||||
}
|
||||
_sample = new Array(_sample_size);
|
||||
_sampleIdx = 0;
|
||||
}
|
||||
}
|
||||
_didAudio = false;
|
||||
}
|
||||
|
||||
function _calcSampleRate() {
|
||||
_cycles_per_sample = _khz * 1000 / _rate;
|
||||
}
|
||||
|
||||
function _updateKHz(khz) {
|
||||
_khz = khz;
|
||||
_calcSampleRate();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
function _access(off, val) {
|
||||
var result = 0;
|
||||
var now = cpu.getCycles();
|
||||
var delta = now - _trigger;
|
||||
switch (off) {
|
||||
case LOC.CLRTEXT:
|
||||
_debug('Graphics Mode');
|
||||
vm.text(false);
|
||||
break;
|
||||
case LOC.SETTEXT:
|
||||
_debug('Text Mode');
|
||||
vm.text(true);
|
||||
break;
|
||||
case LOC.CLRMIXED:
|
||||
_debug('Mixed Mode off');
|
||||
vm.mixed(false);
|
||||
break;
|
||||
case LOC.SETMIXED:
|
||||
_debug('Mixed Mode on');
|
||||
vm.mixed(true);
|
||||
break;
|
||||
case LOC.CLRHIRES:
|
||||
_debug('LoRes Mode');
|
||||
vm.hires(false);
|
||||
break;
|
||||
case LOC.SETHIRES:
|
||||
_debug('HiRes Mode');
|
||||
vm.hires(true);
|
||||
break;
|
||||
case LOC.PAGE1:
|
||||
vm.page(1);
|
||||
break;
|
||||
case LOC.PAGE2:
|
||||
vm.page(2);
|
||||
break;
|
||||
case LOC.SETAN0:
|
||||
_debug('Annunciator 0 on');
|
||||
_annunciators[0] = true;
|
||||
break;
|
||||
case LOC.SETAN1:
|
||||
_debug('Annunciator 1 on');
|
||||
_annunciators[1] = true;
|
||||
break;
|
||||
case LOC.SETAN2:
|
||||
_debug('Annunciator 2 on');
|
||||
_annunciators[2] = true;
|
||||
break;
|
||||
case LOC.SETAN3:
|
||||
_debug('Annunciator 3 on');
|
||||
_annunciators[3] = true;
|
||||
break;
|
||||
case LOC.CLRAN0:
|
||||
_debug('Annunciator 0 off');
|
||||
_annunciators[0] = false;
|
||||
break;
|
||||
case LOC.CLRAN1:
|
||||
_debug('Annunciator 1 off');
|
||||
_annunciators[1] = false;
|
||||
break;
|
||||
case LOC.CLRAN2:
|
||||
_debug('Annunciator 2 off');
|
||||
_annunciators[2] = false;
|
||||
break;
|
||||
case LOC.CLRAN3:
|
||||
_debug('Annunciator 3 off');
|
||||
_annunciators[3] = false;
|
||||
break;
|
||||
case LOC.PB0:
|
||||
result = _button[0] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PB1:
|
||||
result = _button[1] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PB2:
|
||||
result = _button[2] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PADDLE0:
|
||||
result = (delta < (_paddle[0] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE1:
|
||||
result = (delta < (_paddle[1] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE2:
|
||||
result = (delta < (_paddle[2] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE3:
|
||||
result = (delta < (_paddle[3] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.ACCEL:
|
||||
if (val !== undefined) {
|
||||
_updateKHz(val & 0x01 ? 1023 : 4096);
|
||||
}
|
||||
break;
|
||||
case LOC.TAPEIN:
|
||||
if (_tapeOffset == -1) {
|
||||
_tapeOffset = 0;
|
||||
_tapeNext = now;
|
||||
}
|
||||
|
||||
if (_tapeOffset < _tape.length) {
|
||||
_tapeCurrent = _tape[_tapeOffset][1];
|
||||
while (now >= _tapeNext) {
|
||||
if ((_tapeOffset % 1000) === 0) {
|
||||
debug('Read ' + (_tapeOffset / 1000));
|
||||
}
|
||||
_tapeCurrent = _tape[_tapeOffset][1];
|
||||
_tapeNext += _tape[_tapeOffset++][0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result = _tapeCurrent ? 0x80 : 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (off & 0xf0) {
|
||||
case LOC.KEYBOARD: // C00x
|
||||
result = _key;
|
||||
break;
|
||||
case LOC.STROBE: // C01x
|
||||
_key &= 0x7f;
|
||||
if (_buffer.length > 0) {
|
||||
val = _buffer.shift();
|
||||
if (val == '\n') {
|
||||
val = '\r';
|
||||
}
|
||||
_key = val.charCodeAt(0) | 0x80;
|
||||
}
|
||||
result = (_keyDown ? 0x80 : 0x00) | _key;
|
||||
break;
|
||||
case LOC.TAPEOUT: // C02x
|
||||
_phase = -_phase;
|
||||
_didAudio = true;
|
||||
_tick();
|
||||
break;
|
||||
case LOC.SPEAKER: // C03x
|
||||
_phase = -_phase;
|
||||
_didAudio = true;
|
||||
_tick();
|
||||
break;
|
||||
case LOC.C040STB: // C04x
|
||||
// I/O Strobe
|
||||
break;
|
||||
case LOC.PDLTRIG: // C07x
|
||||
_trigger = cpu.getCycles();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (val !== undefined) {
|
||||
result = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
start: function apple2io_start() {
|
||||
return 0xc0;
|
||||
},
|
||||
|
||||
end: function apple2io_end() {
|
||||
return 0xcf;
|
||||
},
|
||||
|
||||
ioSwitch: function apple2io_ioSwitch(off, val) {
|
||||
var result;
|
||||
if (off < 0x80) {
|
||||
result = _access(off, val);
|
||||
} else {
|
||||
var slot = (off & 0x70) >> 4;
|
||||
var card = _slot[slot];
|
||||
if (card && card.ioSwitch) {
|
||||
result = card.ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
reset: function apple2io_reset() {
|
||||
for (var slot = 0; slot < 8; slot++) {
|
||||
var card = _slot[slot];
|
||||
if (card && card.reset) {
|
||||
card.reset();
|
||||
}
|
||||
}
|
||||
vm.reset();
|
||||
},
|
||||
|
||||
blit: function apple2io_blit() {
|
||||
var card = _slot[3];
|
||||
if (card && card.blit) {
|
||||
return card.blit();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
read: function apple2io_read(page, off) {
|
||||
var result = 0;
|
||||
var slot;
|
||||
var card;
|
||||
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
result = this.ioSwitch(off, undefined);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
card = _slot[slot];
|
||||
if (_auxRom != card) {
|
||||
// _debug('Setting auxRom to slot', slot);
|
||||
_auxRom = card;
|
||||
}
|
||||
if (card) {
|
||||
result = card.read(page, off);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_auxRom) {
|
||||
result = _auxRom.read(page, off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
write: function apple2io_write(page, off, val) {
|
||||
var slot;
|
||||
var card;
|
||||
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
this.ioSwitch(off, val);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
card = _slot[slot];
|
||||
if (_auxRom != card) {
|
||||
// _debug('Setting auxRom to slot', slot);
|
||||
_auxRom = card;
|
||||
}
|
||||
if (card) {
|
||||
card.write(page, off, val);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_auxRom) {
|
||||
_auxRom.write(page, off, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
getState: function apple2io_getState() {
|
||||
return {
|
||||
annunciators: _annunciators[0]
|
||||
};
|
||||
},
|
||||
setState: function apple2io_setState(state) {
|
||||
_annunciators = state.annunciators;
|
||||
},
|
||||
|
||||
setSlot: function apple2io_setSlot(slot, card) {
|
||||
_slot[slot] = card;
|
||||
},
|
||||
|
||||
keyDown: function apple2io_keyDown(ascii) {
|
||||
_keyDown = true;
|
||||
_key = ascii | 0x80;
|
||||
},
|
||||
|
||||
keyUp: function apple2io_keyUp() {
|
||||
_keyDown = false;
|
||||
},
|
||||
|
||||
buttonDown: function apple2io_buttonDown(b) {
|
||||
_button[b] = true;
|
||||
},
|
||||
|
||||
buttonUp: function apple2io_buttonUp(b) {
|
||||
_button[b] = false;
|
||||
},
|
||||
|
||||
paddle: function apple2io_paddle(p, v) {
|
||||
_paddle[p] = v;
|
||||
},
|
||||
|
||||
updateKHz: function apple2io_updateKHz(khz) {
|
||||
_updateKHz(khz);
|
||||
},
|
||||
|
||||
getKHz: function apple2io_updateKHz() {
|
||||
return _khz;
|
||||
},
|
||||
|
||||
setKeyBuffer: function apple2io_setKeyBuffer(buffer) {
|
||||
_buffer = buffer.split('');
|
||||
if (_buffer.length > 0) {
|
||||
_keyDown = true;
|
||||
_key = _buffer.shift().charCodeAt(0) | 0x80;
|
||||
}
|
||||
},
|
||||
|
||||
setTape: function apple2io_setTape(tape) {
|
||||
debug('Tape length: ' + tape.length);
|
||||
_tape = tape;
|
||||
_tapeOffset = -1;
|
||||
},
|
||||
|
||||
sampleRate: function sampleRate(rate, sample_size) {
|
||||
_rate = rate;
|
||||
_sample_size = sample_size;
|
||||
_sample = new Array(_sample_size);
|
||||
_sampleIdx = 0;
|
||||
_calcSampleRate();
|
||||
},
|
||||
|
||||
tick: function tick() {
|
||||
_tick();
|
||||
for (var idx = 0; idx < 8; idx++) {
|
||||
if (_slot[idx] && _slot[idx].tick) {
|
||||
_slot[idx].tick();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addSampleListener: function addSampleListener(cb) {
|
||||
_audioListener = cb;
|
||||
},
|
||||
|
||||
annunciator: function annunciator(idx) {
|
||||
return _annunciators[idx];
|
||||
},
|
||||
|
||||
cycles: function apple2io_cycles() {
|
||||
return cpu.getCycles();
|
||||
}
|
||||
};
|
||||
}
|
490
js/apple2io.ts
Normal file
490
js/apple2io.ts
Normal file
@ -0,0 +1,490 @@
|
||||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import CPU6502, { PageHandler } from './cpu6502';
|
||||
import { byte } from './types';
|
||||
import { debug } from './util';
|
||||
|
||||
type slot = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
type button = 0 | 1 | 2;
|
||||
type paddle = 0 | 1 | 2 | 3;
|
||||
type annunciator = 0 | 1 | 2 | 3;
|
||||
|
||||
interface Annunciators {
|
||||
0: boolean,
|
||||
1: boolean,
|
||||
2: boolean,
|
||||
3: boolean,
|
||||
}
|
||||
|
||||
interface State {
|
||||
annunciators: Annunciators;
|
||||
}
|
||||
|
||||
export type SampleListener = (sample: number[]) => void;
|
||||
|
||||
const LOC = {
|
||||
KEYBOARD: 0x00, // keyboard data (latched) (Read),
|
||||
STROBE: 0x10, // clear bit 7 of keyboard data ($C000)
|
||||
TAPEOUT: 0x20, // toggle the cassette output.
|
||||
SPEAKER: 0x30, // toggle speaker diaphragm
|
||||
C040STB: 0x40, // trigger game port sync
|
||||
CLRTEXT: 0x50, // display graphics
|
||||
SETTEXT: 0x51, // display text
|
||||
CLRMIXED: 0x52, // clear mixed mode- enable full graphics
|
||||
SETMIXED: 0x53, // enable graphics/text mixed mode
|
||||
PAGE1: 0x54, // select text/graphics page1
|
||||
PAGE2: 0x55, // select text/graphics page2
|
||||
CLRHIRES: 0x56, // select Lo-res
|
||||
SETHIRES: 0x57, // select Hi-res
|
||||
CLRAN0: 0x58, // Set annunciator-0 output to 0
|
||||
SETAN0: 0x59, // Set annunciator-0 output to 1
|
||||
CLRAN1: 0x5A, // Set annunciator-1 output to 0
|
||||
SETAN1: 0x5B, // Set annunciator-1 output to 1
|
||||
CLRAN2: 0x5C, // Set annunciator-2 output to 0
|
||||
SETAN2: 0x5D, // Set annunciator-2 output to 1
|
||||
CLRAN3: 0x5E, // Set annunciator-3 output to 0
|
||||
SETAN3: 0x5F, // Set annunciator-3 output to 1
|
||||
TAPEIN: 0x60, // bit 7: data from cassette
|
||||
PB0: 0x61, // game Pushbutton 0 / open apple (command) key data
|
||||
PB1: 0x62, // game Pushbutton 1 / closed apple (option) key data
|
||||
PB2: 0x63, // game Pushbutton 2 (read)
|
||||
PADDLE0: 0x64, // bit 7: status of pdl-0 timer (read)
|
||||
PADDLE1: 0x65, // bit 7: status of pdl-1 timer (read)
|
||||
PADDLE2: 0x66, // bit 7: status of pdl-2 timer (read)
|
||||
PADDLE3: 0x67, // bit 7: status of pdl-3 timer (read)
|
||||
PDLTRIG: 0x70, // trigger paddles
|
||||
ACCEL: 0x74, // CPU Speed control
|
||||
};
|
||||
|
||||
export default class Apple2IO implements PageHandler {
|
||||
private _slot: any[] = []; // TODO(flan): Needs typing.
|
||||
private _auxRom: any = null; // TODO(flan): Needs typing.
|
||||
|
||||
private _khz = 1023;
|
||||
private _rate = 44000;
|
||||
private _sample_size = 4096;
|
||||
|
||||
private _cycles_per_sample: number;
|
||||
|
||||
private _buffer: string[] = [];
|
||||
private _key = 0;
|
||||
private _keyDown = false;
|
||||
private _button = [false, false, false];
|
||||
private _paddle = [0.0, 0.0, 0.0, 0, 0];
|
||||
private _phase = -1;
|
||||
private _sample: number[] = [];
|
||||
private _sampleIdx = 0;
|
||||
private _sampleTime = 0;
|
||||
private _didAudio = false;
|
||||
|
||||
private _high = 0.5;
|
||||
private _low = -0.5;
|
||||
|
||||
private _audioListener: SampleListener | undefined;
|
||||
|
||||
private _trigger = 0;
|
||||
private _annunciators: Annunciators = [false, false, false, false];
|
||||
|
||||
private _tape = [];
|
||||
private _tapeOffset = 0;
|
||||
private _tapeNext = 0;
|
||||
private _tapeCurrent = false;
|
||||
|
||||
constructor(private readonly cpu: CPU6502, private readonly vm: any) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this._calcSampleRate();
|
||||
}
|
||||
|
||||
_debug(..._args: any) {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
_tick() {
|
||||
let now = this.cpu.getCycles();
|
||||
let phase = this._didAudio ? (this._phase > 0 ? this._high : this._low) : 0.0;
|
||||
for (; this._sampleTime < now; this._sampleTime += this._cycles_per_sample) {
|
||||
this._sample[this._sampleIdx++] = phase;
|
||||
if (this._sampleIdx === this._sample_size) {
|
||||
if (this._audioListener) {
|
||||
this._audioListener(this._sample);
|
||||
}
|
||||
this._sample = new Array(this._sample_size);
|
||||
this._sampleIdx = 0;
|
||||
}
|
||||
}
|
||||
this._didAudio = false;
|
||||
}
|
||||
|
||||
_calcSampleRate() {
|
||||
this._cycles_per_sample = this._khz * 1000 / this._rate;
|
||||
}
|
||||
|
||||
_updateKHz(khz: number) {
|
||||
this._khz = khz;
|
||||
this._calcSampleRate();
|
||||
}
|
||||
|
||||
_access(off: byte, val?: byte): byte | undefined {
|
||||
let result: number | undefined = 0;
|
||||
let now = this.cpu.getCycles();
|
||||
let delta = now - this._trigger;
|
||||
switch (off) {
|
||||
case LOC.CLRTEXT:
|
||||
this._debug('Graphics Mode');
|
||||
this.vm.text(false);
|
||||
break;
|
||||
case LOC.SETTEXT:
|
||||
this._debug('Text Mode');
|
||||
this.vm.text(true);
|
||||
break;
|
||||
case LOC.CLRMIXED:
|
||||
this._debug('Mixed Mode off');
|
||||
this.vm.mixed(false);
|
||||
break;
|
||||
case LOC.SETMIXED:
|
||||
this._debug('Mixed Mode on');
|
||||
this.vm.mixed(true);
|
||||
break;
|
||||
case LOC.CLRHIRES:
|
||||
this._debug('LoRes Mode');
|
||||
this.vm.hires(false);
|
||||
break;
|
||||
case LOC.SETHIRES:
|
||||
this._debug('HiRes Mode');
|
||||
this.vm.hires(true);
|
||||
break;
|
||||
case LOC.PAGE1:
|
||||
this.vm.page(1);
|
||||
break;
|
||||
case LOC.PAGE2:
|
||||
this.vm.page(2);
|
||||
break;
|
||||
case LOC.SETAN0:
|
||||
this._debug('Annunciator 0 on');
|
||||
this._annunciators[0] = true;
|
||||
break;
|
||||
case LOC.SETAN1:
|
||||
this._debug('Annunciator 1 on');
|
||||
this._annunciators[1] = true;
|
||||
break;
|
||||
case LOC.SETAN2:
|
||||
this._debug('Annunciator 2 on');
|
||||
this._annunciators[2] = true;
|
||||
break;
|
||||
case LOC.SETAN3:
|
||||
this._debug('Annunciator 3 on');
|
||||
this._annunciators[3] = true;
|
||||
break;
|
||||
case LOC.CLRAN0:
|
||||
this._debug('Annunciator 0 off');
|
||||
this._annunciators[0] = false;
|
||||
break;
|
||||
case LOC.CLRAN1:
|
||||
this._debug('Annunciator 1 off');
|
||||
this._annunciators[1] = false;
|
||||
break;
|
||||
case LOC.CLRAN2:
|
||||
this._debug('Annunciator 2 off');
|
||||
this._annunciators[2] = false;
|
||||
break;
|
||||
case LOC.CLRAN3:
|
||||
this._debug('Annunciator 3 off');
|
||||
this._annunciators[3] = false;
|
||||
break;
|
||||
case LOC.PB0:
|
||||
result = this._button[0] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PB1:
|
||||
result = this._button[1] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PB2:
|
||||
result = this._button[2] ? 0x80 : 0;
|
||||
break;
|
||||
case LOC.PADDLE0:
|
||||
result = (delta < (this._paddle[0] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE1:
|
||||
result = (delta < (this._paddle[1] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE2:
|
||||
result = (delta < (this._paddle[2] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.PADDLE3:
|
||||
result = (delta < (this._paddle[3] * 2756) ? 0x80 : 0x00);
|
||||
break;
|
||||
case LOC.ACCEL:
|
||||
if (val !== undefined) {
|
||||
this._updateKHz(val & 0x01 ? 1023 : 4096);
|
||||
}
|
||||
break;
|
||||
case LOC.TAPEIN:
|
||||
if (this._tapeOffset == -1) {
|
||||
this._tapeOffset = 0;
|
||||
this._tapeNext = now;
|
||||
}
|
||||
|
||||
if (this._tapeOffset < this._tape.length) {
|
||||
this._tapeCurrent = this._tape[this._tapeOffset][1];
|
||||
while (now >= this._tapeNext) {
|
||||
if ((this._tapeOffset % 1000) === 0) {
|
||||
debug('Read ' + (this._tapeOffset / 1000));
|
||||
}
|
||||
this._tapeCurrent = this._tape[this._tapeOffset][1];
|
||||
this._tapeNext += this._tape[this._tapeOffset++][0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result = this._tapeCurrent ? 0x80 : 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (off & 0xf0) {
|
||||
case LOC.KEYBOARD: // C00x
|
||||
result = this._key;
|
||||
break;
|
||||
case LOC.STROBE: // C01x
|
||||
this._key &= 0x7f;
|
||||
if (this._buffer.length > 0) {
|
||||
let val = this._buffer.shift() as string;
|
||||
if (val == '\n') {
|
||||
val = '\r';
|
||||
}
|
||||
this._key = val.charCodeAt(0) | 0x80;
|
||||
}
|
||||
result = (this._keyDown ? 0x80 : 0x00) | this._key;
|
||||
break;
|
||||
case LOC.TAPEOUT: // C02x
|
||||
this._phase = -this._phase;
|
||||
this._didAudio = true;
|
||||
this._tick();
|
||||
break;
|
||||
case LOC.SPEAKER: // C03x
|
||||
this._phase = -this._phase;
|
||||
this._didAudio = true;
|
||||
this._tick();
|
||||
break;
|
||||
case LOC.C040STB: // C04x
|
||||
// I/O Strobe
|
||||
break;
|
||||
case LOC.PDLTRIG: // C07x
|
||||
this._trigger = this.cpu.getCycles();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (val !== undefined) {
|
||||
result = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
start() {
|
||||
return 0xc0;
|
||||
}
|
||||
|
||||
end() {
|
||||
return 0xcf;
|
||||
}
|
||||
|
||||
ioSwitch(off: byte, val?: byte) {
|
||||
let result;
|
||||
if (off < 0x80) {
|
||||
result = this._access(off, val);
|
||||
} else {
|
||||
let slot = (off & 0x70) >> 4;
|
||||
let card = this._slot[slot];
|
||||
if (card && card.ioSwitch) {
|
||||
result = card.ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (let slot = 0; slot < 8; slot++) {
|
||||
let card = this._slot[slot];
|
||||
if (card && card.reset) {
|
||||
card.reset();
|
||||
}
|
||||
}
|
||||
this.vm.reset();
|
||||
}
|
||||
|
||||
blit() {
|
||||
let card = this._slot[3];
|
||||
if (card && card.blit) {
|
||||
return card.blit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
var result = 0;
|
||||
var slot;
|
||||
var card;
|
||||
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
result = this.ioSwitch(off, undefined);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
card = this._slot[slot];
|
||||
if (this._auxRom != card) {
|
||||
// _debug('Setting auxRom to slot', slot);
|
||||
this._auxRom = card;
|
||||
}
|
||||
if (card) {
|
||||
result = card.read(page, off);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (this._auxRom) {
|
||||
result = this._auxRom.read(page, off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
write(page: byte, off: byte, val: byte) {
|
||||
let slot;
|
||||
let card;
|
||||
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
this.ioSwitch(off, val);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
card = this._slot[slot];
|
||||
if (this._auxRom != card) {
|
||||
// _debug('Setting auxRom to slot', slot);
|
||||
this._auxRom = card;
|
||||
}
|
||||
if (card) {
|
||||
card.write(page, off, val);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (this._auxRom) {
|
||||
this._auxRom.write(page, off, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
annunciators: this._annunciators[0]
|
||||
};
|
||||
}
|
||||
|
||||
setState(state: State) {
|
||||
this._annunciators = state.annunciators;
|
||||
}
|
||||
|
||||
setSlot(slot: slot, card: byte) {
|
||||
this._slot[slot] = card;
|
||||
}
|
||||
|
||||
keyDown(ascii: byte) {
|
||||
this._keyDown = true;
|
||||
this._key = ascii | 0x80;
|
||||
}
|
||||
|
||||
keyUp() {
|
||||
this._keyDown = false;
|
||||
}
|
||||
|
||||
buttonDown(b: button) {
|
||||
this._button[b] = true;
|
||||
}
|
||||
|
||||
buttonUp(b: button) {
|
||||
this._button[b] = false;
|
||||
}
|
||||
|
||||
paddle(p: paddle, v: byte) {
|
||||
this._paddle[p] = v;
|
||||
}
|
||||
|
||||
updateKHz(khz: number) {
|
||||
this._updateKHz(khz);
|
||||
}
|
||||
|
||||
getKHz() {
|
||||
return this._khz;
|
||||
}
|
||||
|
||||
setKeyBuffer(buffer: string) {
|
||||
this._buffer = buffer.split(''); // split to charaters
|
||||
if (this._buffer.length > 0) {
|
||||
this._keyDown = true;
|
||||
let key = this._buffer.shift() as string; // never undefined
|
||||
this._key = key.charCodeAt(0) | 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
setTape(tape: any) { // TODO(flan): Needs typing.
|
||||
debug('Tape length: ' + tape.length);
|
||||
this._tape = tape;
|
||||
this._tapeOffset = -1;
|
||||
}
|
||||
|
||||
sampleRate(rate: number, sample_size: number) {
|
||||
this._rate = rate;
|
||||
this._sample_size = sample_size;
|
||||
this._sample = new Array(this._sample_size);
|
||||
this._sampleIdx = 0;
|
||||
this._calcSampleRate();
|
||||
}
|
||||
|
||||
tick() {
|
||||
this._tick();
|
||||
for (var idx = 0; idx < 8; idx++) {
|
||||
if (this._slot[idx] && this._slot[idx].tick) {
|
||||
this._slot[idx].tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSampleListener(cb: SampleListener) {
|
||||
this._audioListener = cb;
|
||||
}
|
||||
|
||||
annunciator(idx: annunciator) {
|
||||
return this._annunciators[idx];
|
||||
}
|
||||
|
||||
cycles() {
|
||||
return this.cpu.getCycles();
|
||||
}
|
||||
}
|
1156
js/canvas.js
1156
js/canvas.js
File diff suppressed because it is too large
Load Diff
1202
js/canvas.ts
Normal file
1202
js/canvas.ts
Normal file
File diff suppressed because it is too large
Load Diff
225
js/cpu6502.ts
225
js/cpu6502.ts
@ -52,32 +52,32 @@ type Modes = Record<Mode, number>;
|
||||
/** Addressing mode name to instruction size mapping. */
|
||||
const sizes: Modes = {
|
||||
accumulator: 1,
|
||||
implied: 1,
|
||||
immediate: 2,
|
||||
absolute: 3,
|
||||
zeroPage: 2,
|
||||
relative: 2,
|
||||
implied: 1,
|
||||
immediate: 2,
|
||||
absolute: 3,
|
||||
zeroPage: 2,
|
||||
relative: 2,
|
||||
|
||||
absoluteX: 3,
|
||||
absoluteY: 3,
|
||||
zeroPageX: 2,
|
||||
zeroPageY: 2,
|
||||
absoluteX: 3,
|
||||
absoluteY: 3,
|
||||
zeroPageX: 2,
|
||||
zeroPageY: 2,
|
||||
|
||||
absoluteIndirect: 3,
|
||||
zeroPageXIndirect: 2,
|
||||
zeroPageIndirectY: 2,
|
||||
absoluteIndirect: 3,
|
||||
zeroPageXIndirect: 2,
|
||||
zeroPageIndirectY: 2,
|
||||
|
||||
/* 65c02 */
|
||||
zeroPageIndirect: 2,
|
||||
absoluteXIndirect: 3,
|
||||
zeroPage_relative: 3
|
||||
zeroPageIndirect: 2,
|
||||
absoluteXIndirect: 3,
|
||||
zeroPage_relative: 3
|
||||
};
|
||||
|
||||
/** Status register flag numbers. */
|
||||
type flag = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01;
|
||||
|
||||
/** Flags to status byte mask. */
|
||||
const flags: {[key: string]: flag} = {
|
||||
const flags: { [key: string]: flag } = {
|
||||
N: 0x80, // Negative
|
||||
V: 0x40, // oVerflow
|
||||
B: 0x10, // Break
|
||||
@ -95,38 +95,27 @@ const loc = {
|
||||
BRK: 0xFFFE
|
||||
};
|
||||
|
||||
export interface ReadablePage {
|
||||
export interface Page {
|
||||
read(page: byte, offset: byte): byte;
|
||||
}
|
||||
|
||||
function isReadablePage(page: ReadablePage | any): page is ReadablePage {
|
||||
return (page as ReadablePage).read !== undefined;
|
||||
}
|
||||
|
||||
export interface WriteablePage {
|
||||
write(page: byte, offset: byte, value: byte): void;
|
||||
}
|
||||
|
||||
function isWriteablePage(page: WriteablePage | any): page is WriteablePage {
|
||||
return (page as WriteablePage).write !== undefined;
|
||||
}
|
||||
|
||||
export interface PageHandler {
|
||||
export interface PageHandler extends Page {
|
||||
start(): byte;
|
||||
end(): byte;
|
||||
}
|
||||
|
||||
function isResettablePageHandler(pageHandler: PageHandler | ResettablePageHandler): pageHandler is ResettablePageHandler {
|
||||
return (pageHandler as ResettablePageHandler).reset !== undefined;
|
||||
}
|
||||
|
||||
interface ResettablePageHandler extends PageHandler {
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
const BLANK_PAGE: ReadablePage & WriteablePage = {
|
||||
read: function() { return 0; },
|
||||
write: function() {}
|
||||
function isResettablePageHandler(pageHandler: PageHandler | ResettablePageHandler): pageHandler is ResettablePageHandler {
|
||||
return (pageHandler as ResettablePageHandler).reset !== undefined;
|
||||
}
|
||||
|
||||
const BLANK_PAGE: Page = {
|
||||
read: function () { return 0; },
|
||||
write: function () { }
|
||||
};
|
||||
|
||||
interface Opts {
|
||||
@ -168,8 +157,7 @@ export default class CPU6502 {
|
||||
private yr = 0; // Y Register
|
||||
private sp = 0xff; // Stack Pointer
|
||||
|
||||
private readPages: ReadablePage[] = new Array(0x100);
|
||||
private writePages: WriteablePage[] = new Array(0x100);
|
||||
private memPages: Page[] = new Array(0x100);
|
||||
private resetHandlers: ResettablePageHandler[] = [];
|
||||
private cycles = 0;
|
||||
private sync = false;
|
||||
@ -179,8 +167,8 @@ export default class CPU6502 {
|
||||
constructor(options: CpuOptions = {}) {
|
||||
this.is65C02 = options['65C02'] ? true : false;
|
||||
|
||||
this.readPages.fill(BLANK_PAGE);
|
||||
this.writePages.fill(BLANK_PAGE);
|
||||
this.memPages.fill(BLANK_PAGE);
|
||||
this.memPages.fill(BLANK_PAGE);
|
||||
|
||||
// Create this CPU's instruction table
|
||||
|
||||
@ -275,7 +263,7 @@ export default class CPU6502 {
|
||||
page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
const result = this.readPages[page].read(page, off);
|
||||
const result = this.memPages[page].read(page, off);
|
||||
|
||||
this.pc = (this.pc + 1) & 0xffff;
|
||||
|
||||
@ -288,7 +276,7 @@ export default class CPU6502 {
|
||||
const page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
const result = this.readPages[page].read(page, off);
|
||||
const result = this.memPages[page].read(page, off);
|
||||
|
||||
this.cycles++;
|
||||
|
||||
@ -299,14 +287,14 @@ export default class CPU6502 {
|
||||
const page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
return this.readPages[page].read(page, off);
|
||||
return this.memPages[page].read(page, off);
|
||||
}
|
||||
|
||||
private writeByte(addr: word, val: byte) {
|
||||
const page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
this.writePages[page].write(page, off, val);
|
||||
this.memPages[page].write(page, off, val);
|
||||
|
||||
this.cycles++;
|
||||
}
|
||||
@ -632,7 +620,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
inc = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.increment(oldVal);
|
||||
@ -658,7 +646,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
dec = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.decrement(oldVal);
|
||||
@ -689,7 +677,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
asl = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.shiftLeft(oldVal);
|
||||
@ -708,7 +696,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
lsr = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.shiftRight(oldVal);
|
||||
@ -728,7 +716,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
rol = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.rotateLeft(oldVal);
|
||||
@ -748,7 +736,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
ror = (readAddrFn: ReadAddrFn) => {
|
||||
const addr = readAddrFn({rwm: true});
|
||||
const addr = readAddrFn({ rwm: true });
|
||||
const oldVal = this.readByte(addr);
|
||||
this.writeByte(addr, oldVal);
|
||||
const val = this.rotateRight(oldVal);
|
||||
@ -925,7 +913,7 @@ export default class CPU6502 {
|
||||
|
||||
phx = () => { this.readByte(this.pc); this.pushByte(this.xr); }
|
||||
|
||||
plx = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp);this.testNZ(this.xr = this.pullByte()); }
|
||||
plx = () => { this.readByte(this.pc); this.readByte(0x0100 | this.sp); this.testNZ(this.xr = this.pullByte()); }
|
||||
|
||||
phy = () => { this.readByte(this.pc); this.pushByte(this.yr); }
|
||||
|
||||
@ -997,7 +985,7 @@ export default class CPU6502 {
|
||||
const cpu = this;
|
||||
unk = {
|
||||
name: '???',
|
||||
op: function(_impliedFn: ImpliedFn) {
|
||||
op: function (_impliedFn: ImpliedFn) {
|
||||
debug('Unknown OpCode: ' + toHex(b) +
|
||||
' at ' + toHex(cpu.pc - 1, 4));
|
||||
},
|
||||
@ -1020,68 +1008,68 @@ export default class CPU6502 {
|
||||
let off, val;
|
||||
let result = '';
|
||||
switch (m) {
|
||||
case 'implied':
|
||||
break;
|
||||
case 'immediate':
|
||||
result = '#' + toHexOrSymbol(this.readByteDebug(addr));
|
||||
break;
|
||||
case 'absolute':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4);
|
||||
break;
|
||||
case 'zeroPage':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr));
|
||||
break;
|
||||
case 'relative':
|
||||
{
|
||||
let off = this.readByteDebug(addr);
|
||||
case 'implied':
|
||||
break;
|
||||
case 'immediate':
|
||||
result = '#' + toHexOrSymbol(this.readByteDebug(addr));
|
||||
break;
|
||||
case 'absolute':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4);
|
||||
break;
|
||||
case 'zeroPage':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr));
|
||||
break;
|
||||
case 'relative':
|
||||
{
|
||||
let off = this.readByteDebug(addr);
|
||||
if (off > 127) {
|
||||
off -= 256;
|
||||
}
|
||||
addr += off + 1;
|
||||
result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
||||
}
|
||||
break;
|
||||
case 'absoluteX':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X';
|
||||
break;
|
||||
case 'absoluteY':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',Y';
|
||||
break;
|
||||
case 'zeroPageX':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',X';
|
||||
break;
|
||||
case 'zeroPageY':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',Y';
|
||||
break;
|
||||
case 'absoluteIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ')';
|
||||
break;
|
||||
case 'zeroPageXIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ',X)';
|
||||
break;
|
||||
case 'zeroPageIndirectY':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + '),Y';
|
||||
break;
|
||||
case 'accumulator':
|
||||
result = 'A';
|
||||
break;
|
||||
case 'zeroPageIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ')';
|
||||
break;
|
||||
case 'absoluteXIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X)';
|
||||
break;
|
||||
case 'zeroPage_relative':
|
||||
val = this.readByteDebug(addr);
|
||||
off = this.readByteDebug(addr + 1);
|
||||
if (off > 127) {
|
||||
off -= 256;
|
||||
}
|
||||
addr += off + 1;
|
||||
result = '' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
||||
}
|
||||
break;
|
||||
case 'absoluteX':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X';
|
||||
break;
|
||||
case 'absoluteY':
|
||||
result = '' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',Y';
|
||||
break;
|
||||
case 'zeroPageX':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',X';
|
||||
break;
|
||||
case 'zeroPageY':
|
||||
result = '' + toHexOrSymbol(this.readByteDebug(addr)) + ',Y';
|
||||
break;
|
||||
case 'absoluteIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ')';
|
||||
break;
|
||||
case 'zeroPageXIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ',X)';
|
||||
break;
|
||||
case 'zeroPageIndirectY':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + '),Y';
|
||||
break;
|
||||
case 'accumulator':
|
||||
result = 'A';
|
||||
break;
|
||||
case 'zeroPageIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readByteDebug(addr)) + ')';
|
||||
break;
|
||||
case 'absoluteXIndirect':
|
||||
result = '(' + toHexOrSymbol(this.readWordDebug(addr), 4) + ',X)';
|
||||
break;
|
||||
case 'zeroPage_relative':
|
||||
val = this.readByteDebug(addr);
|
||||
off = this.readByteDebug(addr + 1);
|
||||
if (off > 127) {
|
||||
off -= 256;
|
||||
}
|
||||
addr += off + 2;
|
||||
result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
addr += off + 2;
|
||||
result = '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(addr, 4) + ' (' + off + ')';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -1136,12 +1124,9 @@ export default class CPU6502 {
|
||||
}
|
||||
}
|
||||
|
||||
public addPageHandler(pho: (PageHandler | ResettablePageHandler) & (ReadablePage | WriteablePage)) {
|
||||
public addPageHandler(pho: (PageHandler | ResettablePageHandler) & Page) {
|
||||
for (let idx = pho.start(); idx <= pho.end(); idx++) {
|
||||
if (isReadablePage(pho))
|
||||
this.readPages[idx] = pho;
|
||||
if (isWriteablePage(pho))
|
||||
this.writePages[idx] = pho;
|
||||
this.memPages[idx] = pho;
|
||||
}
|
||||
if (isResettablePageHandler(pho))
|
||||
this.resetHandlers.push(pho);
|
||||
@ -1193,7 +1178,7 @@ export default class CPU6502 {
|
||||
this.pc = pc;
|
||||
}
|
||||
|
||||
public dumpPC(pc: word, symbols: symbols) {
|
||||
public dumpPC(pc: word | undefined, symbols: symbols) {
|
||||
if (pc === undefined) {
|
||||
pc = this.pc;
|
||||
}
|
||||
@ -1279,7 +1264,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
public registers() {
|
||||
return [this.pc,this.ar,this.xr,this.yr,this.sr,this.sp];
|
||||
return [this.pc, this.ar, this.xr, this.yr, this.sr, this.sp];
|
||||
}
|
||||
|
||||
public getState(): CpuState {
|
||||
@ -1323,11 +1308,11 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
public read(page: byte, off: byte): byte {
|
||||
return this.readPages[page].read(page, off);
|
||||
return this.memPages[page].read(page, off);
|
||||
}
|
||||
|
||||
public write(page: byte, off: byte, val: byte) {
|
||||
this.writePages[page].write(page, off, val);
|
||||
this.memPages[page].write(page, off, val);
|
||||
}
|
||||
|
||||
OPS_6502: Instructions = {
|
||||
|
@ -9,39 +9,56 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { byte, memory } from '../types';
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { bytify, debug, toHex } from '../util';
|
||||
|
||||
export type Disk = {
|
||||
format: string,
|
||||
name: string,
|
||||
volume: byte,
|
||||
tracks: byte[],
|
||||
readOnly: boolean,
|
||||
};
|
||||
|
||||
export type Drive = {
|
||||
format: string,
|
||||
volume: 254,
|
||||
tracks: memory[],
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
}
|
||||
|
||||
|
||||
// var DO = [0x0,0x7,0xE,0x6,0xD,0x5,0xC,0x4,
|
||||
// 0xB,0x3,0xA,0x2,0x9,0x1,0x8,0xF];
|
||||
export var _DO = [
|
||||
0x0,0xD,0xB,0x9,0x7,0x5,0x3,0x1,
|
||||
0xE,0xC,0xA,0x8,0x6,0x4,0x2,0xF
|
||||
export const _DO = [
|
||||
0x0, 0xD, 0xB, 0x9, 0x7, 0x5, 0x3, 0x1,
|
||||
0xE, 0xC, 0xA, 0x8, 0x6, 0x4, 0x2, 0xF
|
||||
];
|
||||
|
||||
// var PO = [0x0,0x8,0x1,0x9,0x2,0xa,0x3,0xb,
|
||||
// 0x4,0xc,0x5,0xd,0x6,0xe,0x7,0xf];
|
||||
export var _PO = [
|
||||
0x0,0x2,0x4,0x6,0x8,0xa,0xc,0xe,
|
||||
0x1,0x3,0x5,0x7,0x9,0xb,0xd,0xf
|
||||
export const _PO = [
|
||||
0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe,
|
||||
0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf
|
||||
];
|
||||
|
||||
// var D13O = [
|
||||
// 0x0, 0xa, 0x7, 0x4, 0x1, 0xb, 0x8, 0x5, 0x2, 0xc, 0x9, 0x6, 0x3
|
||||
// ];
|
||||
|
||||
export var _D13O = [
|
||||
export const _D13O = [
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc
|
||||
];
|
||||
|
||||
var _trans53 = [
|
||||
const _trans53 = [
|
||||
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba,
|
||||
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb,
|
||||
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef,
|
||||
0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff
|
||||
];
|
||||
|
||||
var _trans62 = [
|
||||
const _trans62 = [
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
@ -52,7 +69,7 @@ var _trans62 = [
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
];
|
||||
|
||||
export var detrans62 = [
|
||||
export const detrans62 = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
@ -74,8 +91,7 @@ export var detrans62 = [
|
||||
/**
|
||||
* From Beneath Apple DOS
|
||||
*/
|
||||
|
||||
export function fourXfour(val) {
|
||||
export function fourXfour(val: byte): [xx: byte, yy: byte] {
|
||||
var xx = val & 0xaa;
|
||||
var yy = val & 0x55;
|
||||
|
||||
@ -86,20 +102,17 @@ export function fourXfour(val) {
|
||||
return [xx, yy];
|
||||
}
|
||||
|
||||
export function defourXfour(xx, yy) {
|
||||
export function defourXfour(xx: byte, yy: byte): byte {
|
||||
return ((xx << 1) | 0x01) & yy;
|
||||
}
|
||||
|
||||
export function explodeSector16(volume, track, sector, data) {
|
||||
var checksum;
|
||||
|
||||
var buf = [], idx;
|
||||
|
||||
var gap;
|
||||
export function explodeSector16(volume: byte, track: byte, sector: byte, data: memory) {
|
||||
let buf = [];
|
||||
let gap;
|
||||
|
||||
/*
|
||||
* Gap 1/3 (40/0x28 bytes)
|
||||
*/
|
||||
* Gap 1/3 (40/0x28 bytes)
|
||||
*/
|
||||
|
||||
if (sector === 0) // Gap 1
|
||||
gap = 0x80;
|
||||
@ -107,15 +120,15 @@ export function explodeSector16(volume, track, sector, data) {
|
||||
gap = track === 0 ? 0x28 : 0x26;
|
||||
}
|
||||
|
||||
for (idx = 0; idx < gap; idx++) {
|
||||
for (let idx = 0; idx < gap; idx++) {
|
||||
buf.push(0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Address Field
|
||||
*/
|
||||
* Address Field
|
||||
*/
|
||||
|
||||
checksum = volume ^ track ^ sector;
|
||||
let checksum = volume ^ track ^ sector;
|
||||
buf = buf.concat([0xd5, 0xaa, 0x96]); // Address Prolog D5 AA 96
|
||||
buf = buf.concat(fourXfour(volume));
|
||||
buf = buf.concat(fourXfour(track));
|
||||
@ -124,32 +137,31 @@ export function explodeSector16(volume, track, sector, data) {
|
||||
buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB
|
||||
|
||||
/*
|
||||
* Gap 2 (5 bytes)
|
||||
*/
|
||||
* Gap 2 (5 bytes)
|
||||
*/
|
||||
|
||||
for (idx = 0; idx < 0x05; idx++) {
|
||||
for (let idx = 0; idx < 0x05; idx++) {
|
||||
buf.push(0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Data Field
|
||||
*/
|
||||
* Data Field
|
||||
*/
|
||||
|
||||
buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD
|
||||
|
||||
var nibbles = [];
|
||||
var ptr2 = 0;
|
||||
var ptr6 = 0x56;
|
||||
var idx2, idx6;
|
||||
let nibbles: byte[] = [];
|
||||
const ptr2 = 0;
|
||||
const ptr6 = 0x56;
|
||||
|
||||
for (idx = 0; idx < 0x156; idx++) {
|
||||
for (let idx = 0; idx < 0x156; idx++) {
|
||||
nibbles[idx] = 0;
|
||||
}
|
||||
|
||||
idx2 = 0x55;
|
||||
for (idx6 = 0x101; idx6 >= 0; idx6--) {
|
||||
var val6 = data[idx6 % 0x100];
|
||||
var val2 = nibbles[ptr2 + idx2];
|
||||
let idx2 = 0x55;
|
||||
for (let idx6 = 0x101; idx6 >= 0; idx6--) {
|
||||
let val6 = data[idx6 % 0x100];
|
||||
let val2: byte = nibbles[ptr2 + idx2];
|
||||
|
||||
val2 = (val2 << 1) | (val6 & 1);
|
||||
val6 >>= 1;
|
||||
@ -163,9 +175,9 @@ export function explodeSector16(volume, track, sector, data) {
|
||||
idx2 = 0x55;
|
||||
}
|
||||
|
||||
var last = 0;
|
||||
for (idx = 0; idx < 0x156; idx++) {
|
||||
var val = nibbles[idx];
|
||||
let last = 0;
|
||||
for (let idx = 0; idx < 0x156; idx++) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans62[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
@ -174,24 +186,21 @@ export function explodeSector16(volume, track, sector, data) {
|
||||
buf = buf.concat([0xde, 0xaa, 0xf2]); // Epilog DE AA F2
|
||||
|
||||
/*
|
||||
* Gap 3
|
||||
*/
|
||||
* Gap 3
|
||||
*/
|
||||
|
||||
buf.push(0xff);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function explodeSector13(volume, track, sector, data) {
|
||||
var checksum;
|
||||
|
||||
var buf = [], idx;
|
||||
|
||||
var gap;
|
||||
export function explodeSector13(volume: byte, track: byte, sector: byte, data: byte[]) {
|
||||
let buf = [];
|
||||
let gap;
|
||||
|
||||
/*
|
||||
* Gap 1/3 (40/0x28 bytes)
|
||||
*/
|
||||
* Gap 1/3 (40/0x28 bytes)
|
||||
*/
|
||||
|
||||
if (sector === 0) // Gap 1
|
||||
gap = 0x80;
|
||||
@ -199,15 +208,15 @@ export function explodeSector13(volume, track, sector, data) {
|
||||
gap = track === 0 ? 0x28 : 0x26;
|
||||
}
|
||||
|
||||
for (idx = 0; idx < gap; idx++) {
|
||||
for (let idx = 0; idx < gap; idx++) {
|
||||
buf.push(0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Address Field
|
||||
*/
|
||||
* Address Field
|
||||
*/
|
||||
|
||||
checksum = volume ^ track ^ sector;
|
||||
let checksum = volume ^ track ^ sector;
|
||||
buf = buf.concat([0xd5, 0xaa, 0xb5]); // Address Prolog D5 AA B5
|
||||
buf = buf.concat(fourXfour(volume));
|
||||
buf = buf.concat(fourXfour(track));
|
||||
@ -216,23 +225,23 @@ export function explodeSector13(volume, track, sector, data) {
|
||||
buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB
|
||||
|
||||
/*
|
||||
* Gap 2 (5 bytes)
|
||||
*/
|
||||
* Gap 2 (5 bytes)
|
||||
*/
|
||||
|
||||
for (idx = 0; idx < 0x05; idx++) {
|
||||
for (let idx = 0; idx < 0x05; idx++) {
|
||||
buf.push(0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Data Field
|
||||
*/
|
||||
* Data Field
|
||||
*/
|
||||
|
||||
buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD
|
||||
|
||||
var nibbles = [];
|
||||
let nibbles = [];
|
||||
|
||||
var jdx = 0;
|
||||
for (idx = 0x32; idx >= 0; idx--) {
|
||||
let jdx = 0;
|
||||
for (let idx = 0x32; idx >= 0; idx--) {
|
||||
var a5 = data[jdx] >> 3;
|
||||
var a3 = data[jdx] & 0x07;
|
||||
jdx++;
|
||||
@ -254,21 +263,20 @@ export function explodeSector13(volume, track, sector, data) {
|
||||
nibbles[idx + 0x99] = d5;
|
||||
nibbles[idx + 0xcc] = e5;
|
||||
nibbles[idx + 0x100] = a3 << 2 | (d3 & 0x4) >> 1 | (e3 & 0x4) >> 2;
|
||||
nibbles[idx + 0x133] = b3 << 2 | (d3 & 0x2) | (e3 & 0x2) >> 1;
|
||||
nibbles[idx + 0x133] = b3 << 2 | (d3 & 0x2) | (e3 & 0x2) >> 1;
|
||||
nibbles[idx + 0x166] = c3 << 2 | (d3 & 0x1) << 1 | (e3 & 0x1);
|
||||
}
|
||||
nibbles[0xff] = data[jdx] >> 3;
|
||||
nibbles[0x199] = data[jdx] & 0x07;
|
||||
|
||||
var val;
|
||||
var last = 0;
|
||||
for (idx = 0x199; idx >= 0x100; idx--) {
|
||||
val = nibbles[idx];
|
||||
let last = 0;
|
||||
for (let idx = 0x199; idx >= 0x100; idx--) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans53[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
for (idx = 0x0; idx < 0x100; idx++) {
|
||||
val = nibbles[idx];
|
||||
for (let idx = 0x0; idx < 0x100; idx++) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans53[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
@ -277,20 +285,20 @@ export function explodeSector13(volume, track, sector, data) {
|
||||
buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB
|
||||
|
||||
/*
|
||||
* Gap 3
|
||||
*/
|
||||
* Gap 3
|
||||
*/
|
||||
|
||||
buf.push(0xff);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function readSector(drive, track, sector) {
|
||||
var _sector = drive.fmt == 'po' ? _PO[sector] : _DO[sector];
|
||||
var val, state = 0;
|
||||
var idx = 0;
|
||||
var retry = 0;
|
||||
var cur = drive.tracks[track];
|
||||
export function readSector(drive: Drive, track: byte, sector: byte) {
|
||||
let _sector = drive.format == 'po' ? _PO[sector] : _DO[sector];
|
||||
let val, state = 0;
|
||||
let idx = 0;
|
||||
let retry = 0;
|
||||
let cur = drive.tracks[track];
|
||||
|
||||
function _readNext() {
|
||||
var result = cur[idx++];
|
||||
@ -300,97 +308,99 @@ export function readSector(drive, track, sector) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function _skipBytes(count) {
|
||||
function _skipBytes(count: number) {
|
||||
idx += count;
|
||||
if (idx >= cur.length) {
|
||||
idx %= cur.length;
|
||||
retry++;
|
||||
}
|
||||
}
|
||||
var t = 0, s = 0, v = 0, jdx, kdx, checkSum;
|
||||
var data = [];
|
||||
let t = 0, s = 0, v = 0, checkSum;
|
||||
let data = [];
|
||||
while (retry < 4) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
val = _readNext();
|
||||
state = (val === 0xd5) ? 1 : 0;
|
||||
break;
|
||||
case 1:
|
||||
val = _readNext();
|
||||
state = (val === 0xaa) ? 2 : 0;
|
||||
break;
|
||||
case 2:
|
||||
val = _readNext();
|
||||
state = (val === 0x96) ? 3 : (val === 0xad ? 4 : 0);
|
||||
break;
|
||||
case 3: // Address
|
||||
v = defourXfour(_readNext(), _readNext()); // Volume
|
||||
t = defourXfour(_readNext(), _readNext());
|
||||
s = defourXfour(_readNext(), _readNext());
|
||||
checkSum = defourXfour(_readNext(), _readNext());
|
||||
if (checkSum != (v ^ t ^ s)) {
|
||||
debug('Invalid header checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum));
|
||||
}
|
||||
_skipBytes(3); // Skip footer
|
||||
state = 0;
|
||||
break;
|
||||
case 4: // Data
|
||||
if (s === _sector && t === track) {
|
||||
var data2 = [];
|
||||
var last = 0;
|
||||
for (jdx = 0x55; jdx >= 0; jdx--) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
data2[jdx] = val;
|
||||
last = val;
|
||||
case 0:
|
||||
val = _readNext();
|
||||
state = (val === 0xd5) ? 1 : 0;
|
||||
break;
|
||||
case 1:
|
||||
val = _readNext();
|
||||
state = (val === 0xaa) ? 2 : 0;
|
||||
break;
|
||||
case 2:
|
||||
val = _readNext();
|
||||
state = (val === 0x96) ? 3 : (val === 0xad ? 4 : 0);
|
||||
break;
|
||||
case 3: // Address
|
||||
v = defourXfour(_readNext(), _readNext()); // Volume
|
||||
t = defourXfour(_readNext(), _readNext());
|
||||
s = defourXfour(_readNext(), _readNext());
|
||||
checkSum = defourXfour(_readNext(), _readNext());
|
||||
if (checkSum != (v ^ t ^ s)) {
|
||||
debug('Invalid header checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum));
|
||||
}
|
||||
for (jdx = 0; jdx < 0x100; jdx++) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
data[jdx] = val;
|
||||
last = val;
|
||||
}
|
||||
checkSum = detrans62[_readNext() - 0x80] ^ last;
|
||||
if (checkSum) {
|
||||
debug('Invalid data checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum));
|
||||
}
|
||||
for (kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) {
|
||||
data[kdx] <<= 1;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
_skipBytes(3); // Skip footer
|
||||
state = 0;
|
||||
break;
|
||||
case 4: // Data
|
||||
if (s === _sector && t === track) {
|
||||
var data2 = [];
|
||||
var last = 0;
|
||||
for (let jdx = 0x55; jdx >= 0; jdx--) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
data2[jdx] = val;
|
||||
last = val;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
|
||||
data[kdx] <<= 1;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
for (let jdx = 0; jdx < 0x100; jdx++) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
data[jdx] = val;
|
||||
last = val;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
checkSum = detrans62[_readNext() - 0x80] ^ last;
|
||||
if (checkSum) {
|
||||
debug('Invalid data checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum));
|
||||
}
|
||||
for (let kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) {
|
||||
data[kdx] <<= 1;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
|
||||
if (--jdx < 0) jdx = 0x55;
|
||||
data[kdx] <<= 1;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
|
||||
if (--jdx < 0) jdx = 0x55;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
else
|
||||
_skipBytes(0x159); // Skip data, checksum and footer
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
else
|
||||
_skipBytes(0x159); // Skip data, checksum and footer
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function jsonEncode(cur, pretty) {
|
||||
var data = [];
|
||||
var format = 'dsk';
|
||||
for (var t = 0; t < cur.tracks.length; t++) {
|
||||
export function jsonEncode(cur: Drive, pretty: boolean) {
|
||||
// For 'nib', tracks are encoded as strings. For all other formats,
|
||||
// tracks are arrays of sectors which are encoded as strings.
|
||||
let data: string[] | string[][] = [];
|
||||
let format = 'dsk';
|
||||
for (let t = 0; t < cur.tracks.length; t++) {
|
||||
data[t] = [];
|
||||
if (cur.format === 'nib') {
|
||||
format = 'nib';
|
||||
data[t] = base64_encode(cur.tracks[t]);
|
||||
} else {
|
||||
for (var s = 0; s < 0x10; s++) {
|
||||
data[t][s] = base64_encode(readSector(cur, t, s));
|
||||
(data[t] as string[])[s] = base64_encode(readSector(cur, t, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -398,28 +408,33 @@ export function jsonEncode(cur, pretty) {
|
||||
'type': format,
|
||||
'encoding': 'base64',
|
||||
'volume': cur.volume,
|
||||
'data': data
|
||||
}, null, pretty ? ' ' : null);
|
||||
'data': data,
|
||||
'readOnly': cur.readOnly,
|
||||
}, undefined, pretty ? ' ' : undefined);
|
||||
}
|
||||
|
||||
export function jsonDecode(data) {
|
||||
var cur = {};
|
||||
var tracks = [];
|
||||
var json = JSON.parse(data);
|
||||
var v = json.volume;
|
||||
for (var t = 0; t < json.data.length; t++) {
|
||||
var track = [];
|
||||
for (var s = 0; s < json.data[t].length; s++) {
|
||||
var _s = 15 - s;
|
||||
var d = base64_decode(json.data[t][_s]);
|
||||
export function jsonDecode(data: string) {
|
||||
let tracks: memory[] = [];
|
||||
let json = JSON.parse(data);
|
||||
let v = json.volume;
|
||||
let readOnly = json.readOnly;
|
||||
for (let t = 0; t < json.data.length; t++) {
|
||||
let track: byte[] = [];
|
||||
for (let s = 0; s < json.data[t].length; s++) {
|
||||
let _s = 15 - s;
|
||||
let sector: string = json.data[t][_s];
|
||||
const d = base64_decode(sector);
|
||||
track = track.concat(explodeSector16(v, t, s, d));
|
||||
}
|
||||
tracks[t] = bytify(track);
|
||||
}
|
||||
cur.volume = v;
|
||||
cur.format = json.type;
|
||||
cur.tracks = tracks;
|
||||
cur.trackMap = null;
|
||||
let cur: Drive = {
|
||||
volume: v,
|
||||
format: json.type,
|
||||
tracks,
|
||||
readOnly,
|
||||
dirty: false,
|
||||
};
|
||||
|
||||
return cur;
|
||||
}
|
@ -17,7 +17,7 @@ import apple2lc_charset from './roms/apple2lc_char';
|
||||
import pigfont_charset from './roms/pigfont_char';
|
||||
|
||||
import Apple2ROM from './roms/fpbasic';
|
||||
import Apple2jROM from './roms/apple2j_char';
|
||||
import Apple2jROM from './roms/apple2j';
|
||||
import IntBASIC from './roms/intbasic';
|
||||
import OriginalROM from './roms/original';
|
||||
|
||||
|
829
js/mmu.js
829
js/mmu.js
@ -1,829 +0,0 @@
|
||||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import RAM from './ram';
|
||||
import { debug, toHex } from './util';
|
||||
|
||||
export default function MMU(cpu, vm, lores1, lores2, hires1, hires2, io, rom)
|
||||
{
|
||||
var idx;
|
||||
|
||||
var _readPages = new Array(0x100);
|
||||
var _writePages = new Array(0x100);
|
||||
var _pages = new Array(0x100);
|
||||
|
||||
// Language Card RAM Softswitches
|
||||
var _bank1;
|
||||
var _readbsr;
|
||||
var _writebsr;
|
||||
var _prewrite;
|
||||
|
||||
// Auxillary ROM
|
||||
var _intcxrom;
|
||||
var _slot3rom;
|
||||
var _intc8rom;
|
||||
|
||||
// Auxillary RAM
|
||||
var _auxRamRead;
|
||||
var _auxRamWrite;
|
||||
var _altzp;
|
||||
|
||||
// Video
|
||||
var _80store;
|
||||
var _page2;
|
||||
var _hires;
|
||||
|
||||
var _iouDisable;
|
||||
|
||||
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,
|
||||
|
||||
CLR80VID: 0x0C, // clear 80 column mode
|
||||
SET80VID: 0x0D, // set 80 column mode
|
||||
CLRALTCH: 0x0E, // clear mousetext
|
||||
SETALTCH: 0x0F, // set mousetext
|
||||
|
||||
// Status
|
||||
BSRBANK2: 0x11,
|
||||
BSRREADRAM: 0x12,
|
||||
RAMRD: 0x13,
|
||||
RAMWRT: 0x14,
|
||||
INTCXROM: 0x15,
|
||||
ALTZP: 0x16,
|
||||
SLOTC3ROM: 0x17,
|
||||
_80STORE: 0x18,
|
||||
VERTBLANK: 0x19,
|
||||
|
||||
RDTEXT: 0x1A, // using text mode
|
||||
RDMIXED: 0x1B, // using mixed mode
|
||||
RDPAGE2: 0x1C, // using text/graphics page2
|
||||
RDHIRES: 0x1D, // using Hi-res graphics mode
|
||||
RDALTCH: 0x1E, // using alternate character set
|
||||
RD80VID: 0x1F, // using 80-column display mode
|
||||
|
||||
PAGE1: 0x54, // select text/graphics page1 main/aux
|
||||
PAGE2: 0x55, // select text/graphics page2 main/aux
|
||||
RESET_HIRES: 0x56,
|
||||
SET_HIRES: 0x57,
|
||||
|
||||
DHIRESON: 0x5E, // Enable double hires
|
||||
DHIRESOFF: 0x5F, // Disable double hires
|
||||
|
||||
BANK: 0x73, // Back switched RAM card bank
|
||||
|
||||
IOUDISON: 0x7E, // W IOU Disable on / R7 IOU Disable
|
||||
IOUDISOFF: 0x7F, // W IOU Disable off / R7 Double Hires
|
||||
|
||||
// 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;
|
||||
_writebsr = false;
|
||||
_prewrite = false;
|
||||
|
||||
_auxRamRead = false;
|
||||
_auxRamWrite = false;
|
||||
_altzp = false;
|
||||
|
||||
_intcxrom = false;
|
||||
_slot3rom = false;
|
||||
_intc8rom = false;
|
||||
|
||||
_80store = false;
|
||||
_page2 = false;
|
||||
_hires = false;
|
||||
|
||||
_iouDisable = true;
|
||||
}
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
function Switches() {
|
||||
var locs = {};
|
||||
|
||||
Object.keys(LOC).forEach(function(loc) {
|
||||
locs[LOC[loc]] = loc;
|
||||
});
|
||||
|
||||
return {
|
||||
start: function() {
|
||||
return 0xC0;
|
||||
},
|
||||
end: function() {
|
||||
return 0xC0;
|
||||
},
|
||||
read: function(page, off) {
|
||||
var result;
|
||||
if (off in locs) {
|
||||
result = _access(off);
|
||||
} else {
|
||||
result = io.ioSwitch(off, undefined);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
write: function(page, off, val) {
|
||||
if (off in locs) {
|
||||
_access(off, val);
|
||||
} else {
|
||||
io.ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function AuxRom() {
|
||||
return {
|
||||
read: function(page, off) {
|
||||
if (page == 0xc3) {
|
||||
_intc8rom = true;
|
||||
_updateBanks();
|
||||
}
|
||||
if (page == 0xcf && off == 0xff) {
|
||||
_intc8rom = false;
|
||||
_updateBanks();
|
||||
}
|
||||
return rom.read(page, off);
|
||||
},
|
||||
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)];
|
||||
var mem04_07 = [lores1.bank0(), lores1.bank1()];
|
||||
var mem08_0B = [lores2.bank0(), lores2.bank1()];
|
||||
var mem0C_1F = [new RAM(0xC, 0x1F), new RAM(0xC, 0x1F)];
|
||||
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 = [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)
|
||||
];
|
||||
var memE0_FF = [rom, new RAM(0xE0,0xFF), new RAM(0xE0,0xFF)];
|
||||
|
||||
/*
|
||||
* Initialize read/write banks
|
||||
*/
|
||||
|
||||
// Zero Page/Stack
|
||||
for (idx = 0x0; idx < 0x2; idx++) {
|
||||
_pages[idx] = mem00_01;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// 0x300-0x400
|
||||
for (idx = 0x2; idx < 0x4; idx++) {
|
||||
_pages[idx] = mem02_03;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Text Page 1
|
||||
for (idx = 0x4; idx < 0x8; idx++) {
|
||||
_pages[idx] = mem04_07;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Text Page 2
|
||||
for (idx = 0x8; idx < 0xC; idx++) {
|
||||
_pages[idx] = mem08_0B;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// 0xC00-0x2000
|
||||
for (idx = 0xC; idx < 0x20; idx++) {
|
||||
_pages[idx] = mem0C_1F;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Hires Page 1
|
||||
for (idx = 0x20; idx < 0x40; idx++) {
|
||||
_pages[idx] = mem20_3F;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Hires Page 2
|
||||
for (idx = 0x40; idx < 0x60; idx++) {
|
||||
_pages[idx] = mem40_5F;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// 0x6000-0xc000
|
||||
for (idx = 0x60; idx < 0xc0; idx++) {
|
||||
_pages[idx] = mem60_BF;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// I/O Switches
|
||||
idx = 0xc0;
|
||||
_pages[idx] = memC0_C0;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
// Slots
|
||||
for (idx = 0xc1; idx < 0xd0; idx++) {
|
||||
_pages[idx] = memC1_CF;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Basic ROM
|
||||
for (idx = 0xd0; idx < 0xe0; idx++) {
|
||||
_pages[idx] = memD0_DF;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
// Monitor ROM
|
||||
for (idx = 0xe0; idx < 0x100; idx++) {
|
||||
_pages[idx] = memE0_FF;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
|
||||
function _updateBanks() {
|
||||
if (_auxRamRead) {
|
||||
for (idx = 0x02; idx < 0xC0; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0x02; idx < 0xC0; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (_auxRamWrite) {
|
||||
for (idx = 0x02; idx < 0xC0; idx++) {
|
||||
_writePages[idx] = _pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0x02; idx < 0xC0; idx++) {
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (_80store) {
|
||||
if (_page2) {
|
||||
for (idx = 0x4; idx < 0x8; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
_writePages[idx] = _pages[idx][1];
|
||||
}
|
||||
if (_hires) {
|
||||
for (idx = 0x20; idx < 0x40; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
_writePages[idx] = _pages[idx][1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (idx = 0x4; idx < 0x8; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
if (_hires) {
|
||||
for (idx = 0x20; idx < 0x40; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_intcxrom) {
|
||||
for (idx = 0xc1; idx < 0xd0; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0xc1; idx < 0xd0; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
}
|
||||
if (!_slot3rom) {
|
||||
_readPages[0xc3] = _pages[0xc3][1];
|
||||
}
|
||||
if (_intc8rom) {
|
||||
for (idx = 0xc8; idx < 0xd0; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_altzp) {
|
||||
for (idx = 0x0; idx < 0x2; idx++) {
|
||||
_readPages[idx] = _pages[idx][1];
|
||||
_writePages[idx] = _pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0x0; idx < 0x2; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (_readbsr) {
|
||||
if (_bank1) {
|
||||
for (idx = 0xd0; idx < 0xe0; idx++) {
|
||||
_readPages[idx] = _pages[idx][_altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0xd0; idx < 0xe0; idx++) {
|
||||
_readPages[idx] = _pages[idx][_altzp ? 4 : 3];
|
||||
}
|
||||
}
|
||||
for (idx = 0xe0; idx < 0x100; idx++) {
|
||||
_readPages[idx] = _pages[idx][_altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0xd0; idx < 0x100; idx++) {
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (_writebsr) {
|
||||
if (_bank1) {
|
||||
for (idx = 0xd0; idx < 0xe0; idx++) {
|
||||
_writePages[idx] = _pages[idx][_altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0xd0; idx < 0xe0; idx++) {
|
||||
_writePages[idx] = _pages[idx][_altzp ? 4 : 3];
|
||||
}
|
||||
}
|
||||
for (idx = 0xe0; idx < 0x100; idx++) {
|
||||
_writePages[idx] = _pages[idx][_altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (idx = 0xd0; idx < 0x100; idx++) {
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The Big Switch
|
||||
*/
|
||||
|
||||
function _access(off, val) {
|
||||
var result;
|
||||
var readMode = val === undefined;
|
||||
var writeMode = val !== undefined;
|
||||
switch (off) {
|
||||
|
||||
// Apple //e memory management
|
||||
|
||||
case LOC._80STOREOFF:
|
||||
if (writeMode) {
|
||||
_80store = false;
|
||||
_debug('80 Store Off');
|
||||
vm.page(_page2 ? 2 : 1);
|
||||
} else {
|
||||
// Chain to io for keyboard
|
||||
result = io.ioSwitch(off, val);
|
||||
}
|
||||
break;
|
||||
case LOC._80STOREON:
|
||||
if (writeMode) {
|
||||
_80store = true;
|
||||
_debug('80 Store On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMRDOFF:
|
||||
if (writeMode) {
|
||||
_auxRamRead = false;
|
||||
_debug('Aux RAM Read Off');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMRDON:
|
||||
if (writeMode) {
|
||||
_auxRamRead = true;
|
||||
_debug('Aux RAM Read On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMWROFF:
|
||||
if (writeMode) {
|
||||
_auxRamWrite = false;
|
||||
_debug('Aux RAM Write Off');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMWRON:
|
||||
if (writeMode) {
|
||||
_auxRamWrite = true;
|
||||
_debug('Aux RAM Write On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case LOC.INTCXROMOFF:
|
||||
if (writeMode) {
|
||||
_intcxrom = false;
|
||||
_intc8rom = false;
|
||||
_debug('Int CX ROM Off');
|
||||
}
|
||||
break;
|
||||
case LOC.INTCXROMON:
|
||||
if (writeMode) {
|
||||
_intcxrom = true;
|
||||
_debug('Int CX ROM On');
|
||||
}
|
||||
break;
|
||||
case LOC.ALTZPOFF: // 0x08
|
||||
if (writeMode) {
|
||||
_altzp = false;
|
||||
_debug('Alt ZP Off');
|
||||
}
|
||||
break;
|
||||
case LOC.ALTZPON: // 0x09
|
||||
if (writeMode) {
|
||||
_altzp = true;
|
||||
_debug('Alt ZP On');
|
||||
}
|
||||
break;
|
||||
case LOC.SLOTC3ROMOFF: // 0x0A
|
||||
if (writeMode) {
|
||||
_slot3rom = false;
|
||||
_debug('Slot 3 ROM Off');
|
||||
}
|
||||
break;
|
||||
case LOC.SLOTC3ROMON: // 0x0B
|
||||
if (writeMode) {
|
||||
_slot3rom = true;
|
||||
_debug('Slot 3 ROM On');
|
||||
}
|
||||
break;
|
||||
|
||||
// Graphics Switches
|
||||
|
||||
case LOC.CLR80VID:
|
||||
if (writeMode) {
|
||||
_debug('80 Column Mode off');
|
||||
vm._80col(false);
|
||||
}
|
||||
break;
|
||||
case LOC.SET80VID:
|
||||
if (writeMode) {
|
||||
_debug('80 Column Mode on');
|
||||
vm._80col(true);
|
||||
}
|
||||
break;
|
||||
case LOC.CLRALTCH:
|
||||
if (writeMode) {
|
||||
_debug('Alt Char off');
|
||||
vm.altchar(false);
|
||||
}
|
||||
break;
|
||||
case LOC.SETALTCH:
|
||||
if (writeMode) {
|
||||
_debug('Alt Char on');
|
||||
vm.altchar(true);
|
||||
}
|
||||
break;
|
||||
case LOC.PAGE1:
|
||||
_page2 = false;
|
||||
if (!_80store) {
|
||||
result = io.ioSwitch(off, val);
|
||||
}
|
||||
_debug('Page 2 off');
|
||||
break;
|
||||
case LOC.PAGE2:
|
||||
_page2 = true;
|
||||
if (!_80store) {
|
||||
result = io.ioSwitch(off, val);
|
||||
}
|
||||
_debug('Page 2 on');
|
||||
break;
|
||||
|
||||
case LOC.RESET_HIRES:
|
||||
_hires = false;
|
||||
result = io.ioSwitch(off, val);
|
||||
_debug('Hires off');
|
||||
break;
|
||||
|
||||
case LOC.DHIRESON:
|
||||
if (_iouDisable) {
|
||||
vm.doubleHires(true);
|
||||
} else {
|
||||
result = io.ioSwitch(off, val); // an3
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DHIRESOFF:
|
||||
if (_iouDisable) {
|
||||
vm.doubleHires(false);
|
||||
} else {
|
||||
result = io.ioSwitch(off, val); // an3
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.SET_HIRES:
|
||||
_hires = true;
|
||||
result = io.ioSwitch(off, val);
|
||||
_debug('Hires on');
|
||||
break;
|
||||
|
||||
case LOC.IOUDISON:
|
||||
if (writeMode) {
|
||||
_iouDisable = true;
|
||||
}
|
||||
result = _iouDisable ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
case LOC.IOUDISOFF:
|
||||
if (writeMode) {
|
||||
_iouDisable = false;
|
||||
}
|
||||
result = vm.isDoubleHires() ? 0x80 : 0x00;
|
||||
break;
|
||||
|
||||
// Language Card Switches
|
||||
|
||||
case LOC.READBSR2: // 0xC080
|
||||
case LOC._READBSR2: // 0xC084
|
||||
_bank1 = false;
|
||||
_readbsr = true;
|
||||
_writebsr = false;
|
||||
_prewrite = false;
|
||||
// _debug('Bank 2 Read');
|
||||
break;
|
||||
case LOC.WRITEBSR2: // 0xC081
|
||||
case LOC._WRITEBSR2: // 0xC085
|
||||
_bank1 = false;
|
||||
_readbsr = false;
|
||||
if (readMode) { _writebsr = _prewrite; }
|
||||
_prewrite = readMode;
|
||||
// _debug('Bank 2 Write');
|
||||
break;
|
||||
case LOC.OFFBSR2: // 0xC082
|
||||
case LOC._OFFBSR2: // 0xC086
|
||||
_bank1 = false;
|
||||
_readbsr = false;
|
||||
_writebsr = false;
|
||||
_prewrite = false;
|
||||
// _debug('Bank 2 Off');
|
||||
break;
|
||||
case LOC.READWRBSR2: // 0xC083
|
||||
case LOC._READWRBSR2: // 0xC087
|
||||
_bank1 = false;
|
||||
_readbsr = true;
|
||||
if (readMode) { _writebsr = _prewrite; }
|
||||
_prewrite = readMode;
|
||||
// _debug('Bank 2 Read/Write');
|
||||
break;
|
||||
case LOC.READBSR1: // 0xC088
|
||||
case LOC._READBSR1: // 0xC08c
|
||||
_bank1 = true;
|
||||
_readbsr = true;
|
||||
_writebsr = false;
|
||||
_prewrite = false;
|
||||
// _debug('Bank 1 Read');
|
||||
break;
|
||||
case LOC.WRITEBSR1: // 0xC089
|
||||
case LOC._WRITEBSR1: // 0xC08D
|
||||
_bank1 = true;
|
||||
_readbsr = false;
|
||||
if (readMode) { _writebsr = _prewrite; }
|
||||
_prewrite = readMode;
|
||||
// _debug('Bank 1 Write');
|
||||
break;
|
||||
case LOC.OFFBSR1: // 0xC08A
|
||||
case LOC._OFFBSR1: // 0xC08E
|
||||
_bank1 = true;
|
||||
_readbsr = false;
|
||||
_writebsr = false;
|
||||
_prewrite = false;
|
||||
// _debug('Bank 1 Off');
|
||||
break;
|
||||
case LOC.READWRBSR1: // 0xC08B
|
||||
case LOC._READWRBSR1: // 0xC08F
|
||||
_bank1 = true;
|
||||
_readbsr = true;
|
||||
if (readMode) { _writebsr = _prewrite; }
|
||||
_prewrite = readMode;
|
||||
//_debug('Bank 1 Read/Write');
|
||||
break;
|
||||
|
||||
// Status registers
|
||||
|
||||
case LOC.BSRBANK2:
|
||||
_debug('Bank 2 Read ' + !_bank1);
|
||||
result = !_bank1 ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.BSRREADRAM:
|
||||
_debug('Bank SW RAM Read ' + _readbsr);
|
||||
result = _readbsr ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.RAMRD: // 0xC013
|
||||
_debug('Aux RAM Read ' + _auxRamRead);
|
||||
result = _auxRamRead ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RAMWRT: // 0xC014
|
||||
_debug('Aux RAM Write ' + _auxRamWrite);
|
||||
result = _auxRamWrite ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.INTCXROM: // 0xC015
|
||||
// _debug('Int CX ROM ' + _intcxrom);
|
||||
result = _intcxrom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.ALTZP: // 0xC016
|
||||
_debug('Alt ZP ' + _altzp);
|
||||
result = _altzp ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.SLOTC3ROM: // 0xC017
|
||||
_debug('Slot C3 ROM ' + _slot3rom);
|
||||
result = _slot3rom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC._80STORE: // 0xC018
|
||||
_debug('80 Store ' + _80store);
|
||||
result = _80store ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.VERTBLANK: // 0xC019
|
||||
// result = cpu.getCycles() % 20 < 5 ? 0x80 : 0x00;
|
||||
result = (cpu.getCycles() < _vbEnd) ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.RDTEXT:
|
||||
result = vm.isText() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDMIXED:
|
||||
result = vm.isMixed() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDPAGE2:
|
||||
result = vm.isPage2() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDHIRES:
|
||||
result = vm.isHires() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RD80VID:
|
||||
result = vm.is80Col() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDALTCH:
|
||||
result = vm.isAltChar() ? 0x80 : 0x0;
|
||||
break;
|
||||
|
||||
default:
|
||||
debug('MMU missing register ' + toHex(off));
|
||||
break;
|
||||
}
|
||||
|
||||
if (result !== undefined)
|
||||
return result;
|
||||
|
||||
result = 0;
|
||||
|
||||
_updateBanks();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
start: function mmu_start() {
|
||||
lores1.start();
|
||||
lores2.start();
|
||||
return 0x00;
|
||||
},
|
||||
end: function mmu_end() {
|
||||
return 0xff;
|
||||
},
|
||||
reset: function() {
|
||||
debug('reset');
|
||||
_initSwitches();
|
||||
_updateBanks();
|
||||
vm.reset();
|
||||
io.reset();
|
||||
},
|
||||
read: function mmu_read(page, off) {
|
||||
return _readPages[page].read(page, off);
|
||||
},
|
||||
write: function mmu_write(page, off, val) {
|
||||
_writePages[page].write(page, off, val);
|
||||
},
|
||||
resetVB: function mmu_resetVB() {
|
||||
_vbEnd = cpu.getCycles() + 1000;
|
||||
},
|
||||
getState: function() {
|
||||
return {
|
||||
bank1: _bank1,
|
||||
readbsr: _readbsr,
|
||||
writebsr: _writebsr,
|
||||
prewrite: _prewrite,
|
||||
|
||||
intcxrom: _intcxrom,
|
||||
slot3rom: _slot3rom,
|
||||
intc8rom: _intc8rom,
|
||||
|
||||
auxRamRead: _auxRamRead,
|
||||
auxRamWrite: _auxRamWrite,
|
||||
altzp: _altzp,
|
||||
|
||||
_80store: _80store,
|
||||
page2: _page2,
|
||||
hires: _hires,
|
||||
|
||||
mem00_01: [mem00_01[0].getState(), mem00_01[1].getState()],
|
||||
mem02_03: [mem02_03[0].getState(), mem02_03[1].getState()],
|
||||
mem0C_1F: [mem0C_1F[0].getState(), mem0C_1F[1].getState()],
|
||||
mem60_BF: [mem60_BF[0].getState(), mem60_BF[1].getState()],
|
||||
memD0_DF: [
|
||||
memD0_DF[0].getState(), memD0_DF[1].getState(),
|
||||
memD0_DF[2].getState(), memD0_DF[3].getState()
|
||||
],
|
||||
memE0_FF: [memE0_FF[0].getState(), memE0_FF[1].getState()]
|
||||
};
|
||||
},
|
||||
setState: function(state) {
|
||||
_readbsr = state.readbsr;
|
||||
_writebsr = state.writebsr;
|
||||
_bank1 = state.bank1;
|
||||
_prewrite = state.prewrite;
|
||||
|
||||
_intcxrom = state.intcxrom;
|
||||
_slot3rom = state.slot3rom;
|
||||
_intc8rom = state.intc8rom;
|
||||
|
||||
_auxRamRead = state.auxRamRead;
|
||||
_auxRamWrite = state.auxRamWrite;
|
||||
_altzp = state.altzp;
|
||||
|
||||
_80store = state._80store;
|
||||
_page2 = state.page2;
|
||||
_hires = state.hires;
|
||||
|
||||
mem00_01[0].setState(state.mem00_01[0]);
|
||||
mem00_01[1].setState(state.mem00_01[1]);
|
||||
mem02_03[0].setState(state.mem02_03[0]);
|
||||
mem02_03[1].setState(state.mem02_03[1]);
|
||||
mem0C_1F[0].setState(state.mem0C_1F[0]);
|
||||
mem0C_1F[1].setState(state.mem0C_1F[1]);
|
||||
mem60_BF[0].setState(state.mem60_BF[0]);
|
||||
mem60_BF[1].setState(state.mem60_BF[1]);
|
||||
memD0_DF[0].setState(state.memD0_DF[0]);
|
||||
memD0_DF[1].setState(state.memD0_DF[1]);
|
||||
memD0_DF[2].setState(state.memD0_DF[2]);
|
||||
memD0_DF[3].setState(state.memD0_DF[3]);
|
||||
memE0_FF[0].setState(state.memE0_FF[0]);
|
||||
memE0_FF[1].setState(state.memE0_FF[1]);
|
||||
|
||||
_updateBanks();
|
||||
}
|
||||
};
|
||||
}
|
894
js/mmu.ts
Normal file
894
js/mmu.ts
Normal file
@ -0,0 +1,894 @@
|
||||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import CPU6502 from './cpu6502';
|
||||
import RAM from './ram';
|
||||
import { debug, toHex } from './util';
|
||||
import { byte, Memory } from './types';
|
||||
import Apple2IO from './apple2io';
|
||||
import { HiresPage, LoresPage, VideoModes } from './canvas';
|
||||
|
||||
/*
|
||||
* I/O Switch locations
|
||||
*/
|
||||
const 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,
|
||||
|
||||
CLR80VID: 0x0C, // clear 80 column mode
|
||||
SET80VID: 0x0D, // set 80 column mode
|
||||
CLRALTCH: 0x0E, // clear mousetext
|
||||
SETALTCH: 0x0F, // set mousetext
|
||||
|
||||
// Status
|
||||
BSRBANK2: 0x11,
|
||||
BSRREADRAM: 0x12,
|
||||
RAMRD: 0x13,
|
||||
RAMWRT: 0x14,
|
||||
INTCXROM: 0x15,
|
||||
ALTZP: 0x16,
|
||||
SLOTC3ROM: 0x17,
|
||||
_80STORE: 0x18,
|
||||
VERTBLANK: 0x19,
|
||||
|
||||
RDTEXT: 0x1A, // using text mode
|
||||
RDMIXED: 0x1B, // using mixed mode
|
||||
RDPAGE2: 0x1C, // using text/graphics page2
|
||||
RDHIRES: 0x1D, // using Hi-res graphics mode
|
||||
RDALTCH: 0x1E, // using alternate character set
|
||||
RD80VID: 0x1F, // using 80-column display mode
|
||||
|
||||
PAGE1: 0x54, // select text/graphics page1 main/aux
|
||||
PAGE2: 0x55, // select text/graphics page2 main/aux
|
||||
RESET_HIRES: 0x56,
|
||||
SET_HIRES: 0x57,
|
||||
|
||||
DHIRESON: 0x5E, // Enable double hires
|
||||
DHIRESOFF: 0x5F, // Disable double hires
|
||||
|
||||
BANK: 0x73, // Back switched RAM card bank
|
||||
|
||||
IOUDISON: 0x7E, // W IOU Disable on / R7 IOU Disable
|
||||
IOUDISOFF: 0x7F, // W IOU Disable off / R7 Double Hires
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
class Switches implements Memory {
|
||||
// Remapping of LOCS from string -> number to number -> string
|
||||
private locs: { [loc: number]: string } = {};
|
||||
|
||||
constructor(private readonly mmu: MMU, private readonly io: any) {
|
||||
Object.keys(LOC).forEach((loc: keyof typeof LOC) => {
|
||||
let v = LOC[loc];
|
||||
this.locs[v] = loc;
|
||||
});
|
||||
}
|
||||
|
||||
start() {
|
||||
return 0xC0;
|
||||
}
|
||||
|
||||
end() {
|
||||
return 0xC0;
|
||||
}
|
||||
|
||||
read(_page: byte, off: byte) {
|
||||
var result;
|
||||
if (off in this.locs) {
|
||||
result = this.mmu._access(off);
|
||||
} else {
|
||||
result = this.io.ioSwitch(off, undefined);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
write(_page: byte, off: byte, val: byte) {
|
||||
if (off in this.locs) {
|
||||
this.mmu._access(off, val);
|
||||
} else {
|
||||
this.io.ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuxRom {
|
||||
constructor(
|
||||
private readonly mmu: MMU,
|
||||
private readonly rom: Memory) { }
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
if (page == 0xc3) {
|
||||
this.mmu._setIntc8rom(true);
|
||||
this.mmu._updateBanks();
|
||||
}
|
||||
if (page == 0xcf && off == 0xff) {
|
||||
this.mmu._setIntc8rom(false);
|
||||
this.mmu._updateBanks();
|
||||
}
|
||||
return this.rom.read(page, off);
|
||||
}
|
||||
|
||||
write(_page: byte, _off: byte, _val: byte) {
|
||||
// It's ROM.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
interface State {
|
||||
bank1: this._bank1,
|
||||
readbsr: this._readbsr,
|
||||
writebsr: this._writebsr,
|
||||
prewrite: this._prewrite,
|
||||
|
||||
intcxrom: this._intcxrom,
|
||||
slot3rom: this._slot3rom,
|
||||
intc8rom: this._intc8rom,
|
||||
|
||||
auxRamRead: this._auxRamRead,
|
||||
auxRamWrite: this._auxRamWrite,
|
||||
altzp: this._altzp,
|
||||
|
||||
_80store: this._80store,
|
||||
page2: this._page2,
|
||||
hires: this._hires,
|
||||
|
||||
mem00_01: [this.mem00_01[0].getState(), this.mem00_01[1].getState()],
|
||||
mem02_03: [this.mem02_03[0].getState(), this.mem02_03[1].getState()],
|
||||
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
||||
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
||||
memD0_DF: [
|
||||
this.memD0_DF[0].getState(), this.memD0_DF[1].getState(),
|
||||
this.memD0_DF[2].getState(), this.memD0_DF[3].getState()
|
||||
],
|
||||
memE0_FF: [this.memE0_FF[0].getState(), this.memE0_FF[1].getState()]
|
||||
};
|
||||
*/
|
||||
|
||||
export default class MMU implements Memory {
|
||||
private _readPages = new Array(0x100);
|
||||
private _writePages = new Array(0x100);
|
||||
private _pages = new Array(0x100);
|
||||
|
||||
// Language Card RAM Softswitches
|
||||
private _bank1: boolean;
|
||||
private _readbsr: boolean;
|
||||
private _writebsr: boolean;
|
||||
private _prewrite: boolean;
|
||||
|
||||
// Auxillary ROM
|
||||
private _intcxrom: boolean;
|
||||
private _slot3rom: boolean;
|
||||
private _intc8rom: boolean;
|
||||
|
||||
// Auxillary RAM
|
||||
private _auxRamRead: boolean;
|
||||
private _auxRamWrite: boolean;
|
||||
private _altzp: boolean;
|
||||
|
||||
// Video
|
||||
private _80store: boolean;
|
||||
private _page2: boolean;
|
||||
private _hires: boolean;
|
||||
|
||||
private _iouDisable: boolean;
|
||||
|
||||
private _vbEnd = 0;
|
||||
|
||||
private switches = new Switches(this, this.io);
|
||||
private auxRom = new AuxRom(this, this.rom);
|
||||
|
||||
// These fields represent the bank-switched memory ranges.
|
||||
private mem00_01 = [new RAM(0x0, 0x1), new RAM(0x0, 0x1)];
|
||||
private mem02_03 = [new RAM(0x2, 0x3), new RAM(0x2, 0x3)];
|
||||
private mem04_07 = [this.lores1.bank0(), this.lores1.bank1()];
|
||||
private mem08_0B = [this.lores2.bank0(), this.lores2.bank1()];
|
||||
private mem0C_1F = [new RAM(0xC, 0x1F), new RAM(0xC, 0x1F)];
|
||||
private mem20_3F = [this.hires1.bank0(), this.hires1.bank1()];
|
||||
private mem40_5F = [this.hires2.bank0(), this.hires2.bank1()];
|
||||
private mem60_BF = [new RAM(0x60, 0xBF), new RAM(0x60, 0xBF)];
|
||||
private memC0_C0 = [this.switches];
|
||||
private memC1_CF = [this.io, this.auxRom];
|
||||
private memD0_DF = [
|
||||
this.rom,
|
||||
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF),
|
||||
new RAM(0xD0, 0xDF), new RAM(0xD0, 0xDF)
|
||||
];
|
||||
private memE0_FF = [this.rom, new RAM(0xE0, 0xFF), new RAM(0xE0, 0xFF)];
|
||||
|
||||
constructor(
|
||||
private readonly cpu: CPU6502,
|
||||
private readonly vm: VideoModes,
|
||||
private readonly lores1: LoresPage,
|
||||
private readonly lores2: LoresPage,
|
||||
private readonly hires1: HiresPage,
|
||||
private readonly hires2: HiresPage,
|
||||
private readonly io: Apple2IO,
|
||||
// TODO(flan): Better typing.
|
||||
private readonly rom: any) {
|
||||
/*
|
||||
* Initialize read/write banks
|
||||
*/
|
||||
|
||||
// Zero Page/Stack
|
||||
for (let idx = 0x0; idx < 0x2; idx++) {
|
||||
this._pages[idx] = this.mem00_01;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// 0x300-0x400
|
||||
for (let idx = 0x2; idx < 0x4; idx++) {
|
||||
this._pages[idx] = this.mem02_03;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Text Page 1
|
||||
for (let idx = 0x4; idx < 0x8; idx++) {
|
||||
this._pages[idx] = this.mem04_07;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Text Page 2
|
||||
for (let idx = 0x8; idx < 0xC; idx++) {
|
||||
this._pages[idx] = this.mem08_0B;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// 0xC00-0x2000
|
||||
for (let idx = 0xC; idx < 0x20; idx++) {
|
||||
this._pages[idx] = this.mem0C_1F;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Hires Page 1
|
||||
for (let idx = 0x20; idx < 0x40; idx++) {
|
||||
this._pages[idx] = this.mem20_3F;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Hires Page 2
|
||||
for (let idx = 0x40; idx < 0x60; idx++) {
|
||||
this._pages[idx] = this.mem40_5F;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// 0x6000-0xc000
|
||||
for (let idx = 0x60; idx < 0xc0; idx++) {
|
||||
this._pages[idx] = this.mem60_BF;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// I/O Switches
|
||||
{
|
||||
let idx = 0xc0;
|
||||
this._pages[idx] = this.memC0_C0;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Slots
|
||||
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
||||
this._pages[idx] = this.memC1_CF;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Basic ROM
|
||||
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
||||
this._pages[idx] = this.memD0_DF;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
// Monitor ROM
|
||||
for (let idx = 0xe0; idx < 0x100; idx++) {
|
||||
this._pages[idx] = this.memE0_FF;
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
};
|
||||
|
||||
_initSwitches() {
|
||||
this._bank1 = true;
|
||||
this._readbsr = false;
|
||||
this._writebsr = false;
|
||||
this._prewrite = false;
|
||||
|
||||
this._auxRamRead = false;
|
||||
this._auxRamWrite = false;
|
||||
this._altzp = false;
|
||||
|
||||
this._intcxrom = false;
|
||||
this._slot3rom = false;
|
||||
this._intc8rom = false;
|
||||
|
||||
this._80store = false;
|
||||
this._page2 = false;
|
||||
this._hires = false;
|
||||
|
||||
this._iouDisable = true;
|
||||
}
|
||||
|
||||
_debug(..._args: any) {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
_setIntc8rom(on: boolean) {
|
||||
this._intc8rom = on;
|
||||
}
|
||||
|
||||
_updateBanks() {
|
||||
if (this._auxRamRead) {
|
||||
for (let idx = 0x02; idx < 0xC0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0x02; idx < 0xC0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._auxRamWrite) {
|
||||
for (let idx = 0x02; idx < 0xC0; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0x02; idx < 0xC0; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._80store) {
|
||||
if (this._page2) {
|
||||
for (let idx = 0x4; idx < 0x8; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
this._writePages[idx] = this._pages[idx][1];
|
||||
}
|
||||
if (this._hires) {
|
||||
for (let idx = 0x20; idx < 0x40; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
this._writePages[idx] = this._pages[idx][1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0x4; idx < 0x8; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
if (this._hires) {
|
||||
for (let idx = 0x20; idx < 0x40; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._intcxrom) {
|
||||
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0xc1; idx < 0xd0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
}
|
||||
if (!this._slot3rom) {
|
||||
this._readPages[0xc3] = this._pages[0xc3][1];
|
||||
}
|
||||
if (this._intc8rom) {
|
||||
for (let idx = 0xc8; idx < 0xd0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._altzp) {
|
||||
for (let idx = 0x0; idx < 0x2; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][1];
|
||||
this._writePages[idx] = this._pages[idx][1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0x0; idx < 0x2; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._readbsr) {
|
||||
if (this._bank1) {
|
||||
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][this._altzp ? 4 : 3];
|
||||
}
|
||||
}
|
||||
for (let idx = 0xe0; idx < 0x100; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0xd0; idx < 0x100; idx++) {
|
||||
this._readPages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._writebsr) {
|
||||
if (this._bank1) {
|
||||
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0xd0; idx < 0xe0; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][this._altzp ? 4 : 3];
|
||||
}
|
||||
}
|
||||
for (let idx = 0xe0; idx < 0x100; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][this._altzp ? 2 : 1];
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0xd0; idx < 0x100; idx++) {
|
||||
this._writePages[idx] = this._pages[idx][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The Big Switch
|
||||
*/
|
||||
|
||||
_access(off: byte, val?: byte) {
|
||||
let result;
|
||||
let readMode = val === undefined;
|
||||
let writeMode = val !== undefined;
|
||||
switch (off) {
|
||||
|
||||
// Apple //e memory management
|
||||
|
||||
case LOC._80STOREOFF:
|
||||
if (writeMode) {
|
||||
this._80store = false;
|
||||
this._debug('80 Store Off');
|
||||
this.vm.page(this._page2 ? 2 : 1);
|
||||
} else {
|
||||
// Chain to io for keyboard
|
||||
result = this.io.ioSwitch(off, val);
|
||||
}
|
||||
break;
|
||||
case LOC._80STOREON:
|
||||
if (writeMode) {
|
||||
this._80store = true;
|
||||
this._debug('80 Store On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMRDOFF:
|
||||
if (writeMode) {
|
||||
this._auxRamRead = false;
|
||||
this._debug('Aux RAM Read Off');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMRDON:
|
||||
if (writeMode) {
|
||||
this._auxRamRead = true;
|
||||
this._debug('Aux RAM Read On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMWROFF:
|
||||
if (writeMode) {
|
||||
this._auxRamWrite = false;
|
||||
this._debug('Aux RAM Write Off');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
case LOC.RAMWRON:
|
||||
if (writeMode) {
|
||||
this._auxRamWrite = true;
|
||||
this._debug('Aux RAM Write On');
|
||||
} else
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case LOC.INTCXROMOFF:
|
||||
if (writeMode) {
|
||||
this._intcxrom = false;
|
||||
this._intc8rom = false;
|
||||
this._debug('Int CX ROM Off');
|
||||
}
|
||||
break;
|
||||
case LOC.INTCXROMON:
|
||||
if (writeMode) {
|
||||
this._intcxrom = true;
|
||||
this._debug('Int CX ROM On');
|
||||
}
|
||||
break;
|
||||
case LOC.ALTZPOFF: // 0x08
|
||||
if (writeMode) {
|
||||
this._altzp = false;
|
||||
this._debug('Alt ZP Off');
|
||||
}
|
||||
break;
|
||||
case LOC.ALTZPON: // 0x09
|
||||
if (writeMode) {
|
||||
this._altzp = true;
|
||||
this._debug('Alt ZP On');
|
||||
}
|
||||
break;
|
||||
case LOC.SLOTC3ROMOFF: // 0x0A
|
||||
if (writeMode) {
|
||||
this._slot3rom = false;
|
||||
this._debug('Slot 3 ROM Off');
|
||||
}
|
||||
break;
|
||||
case LOC.SLOTC3ROMON: // 0x0B
|
||||
if (writeMode) {
|
||||
this._slot3rom = true;
|
||||
this._debug('Slot 3 ROM On');
|
||||
}
|
||||
break;
|
||||
|
||||
// Graphics Switches
|
||||
|
||||
case LOC.CLR80VID:
|
||||
if (writeMode) {
|
||||
this._debug('80 Column Mode off');
|
||||
this.vm._80col(false);
|
||||
}
|
||||
break;
|
||||
case LOC.SET80VID:
|
||||
if (writeMode) {
|
||||
this._debug('80 Column Mode on');
|
||||
this.vm._80col(true);
|
||||
}
|
||||
break;
|
||||
case LOC.CLRALTCH:
|
||||
if (writeMode) {
|
||||
this._debug('Alt Char off');
|
||||
this.vm.altchar(false);
|
||||
}
|
||||
break;
|
||||
case LOC.SETALTCH:
|
||||
if (writeMode) {
|
||||
this._debug('Alt Char on');
|
||||
this.vm.altchar(true);
|
||||
}
|
||||
break;
|
||||
case LOC.PAGE1:
|
||||
this._page2 = false;
|
||||
if (!this._80store) {
|
||||
result = this.io.ioSwitch(off, val);
|
||||
}
|
||||
this._debug('Page 2 off');
|
||||
break;
|
||||
case LOC.PAGE2:
|
||||
this._page2 = true;
|
||||
if (!this._80store) {
|
||||
result = this.io.ioSwitch(off, val);
|
||||
}
|
||||
this._debug('Page 2 on');
|
||||
break;
|
||||
|
||||
case LOC.RESET_HIRES:
|
||||
this._hires = false;
|
||||
result = this.io.ioSwitch(off, val);
|
||||
this._debug('Hires off');
|
||||
break;
|
||||
|
||||
case LOC.DHIRESON:
|
||||
if (this._iouDisable) {
|
||||
this.vm.doubleHires(true);
|
||||
} else {
|
||||
result = this.io.ioSwitch(off, val); // an3
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DHIRESOFF:
|
||||
if (this._iouDisable) {
|
||||
this.vm.doubleHires(false);
|
||||
} else {
|
||||
result = this.io.ioSwitch(off, val); // an3
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.SET_HIRES:
|
||||
this._hires = true;
|
||||
result = this.io.ioSwitch(off, val);
|
||||
this._debug('Hires on');
|
||||
break;
|
||||
|
||||
case LOC.IOUDISON:
|
||||
if (writeMode) {
|
||||
this._iouDisable = true;
|
||||
}
|
||||
result = this._iouDisable ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
case LOC.IOUDISOFF:
|
||||
if (writeMode) {
|
||||
this._iouDisable = false;
|
||||
}
|
||||
result = this.vm.isDoubleHires() ? 0x80 : 0x00;
|
||||
break;
|
||||
|
||||
// Language Card Switches
|
||||
|
||||
case LOC.READBSR2: // 0xC080
|
||||
case LOC._READBSR2: // 0xC084
|
||||
this._bank1 = false;
|
||||
this._readbsr = true;
|
||||
this._writebsr = false;
|
||||
this._prewrite = false;
|
||||
// _debug('Bank 2 Read');
|
||||
break;
|
||||
case LOC.WRITEBSR2: // 0xC081
|
||||
case LOC._WRITEBSR2: // 0xC085
|
||||
this._bank1 = false;
|
||||
this._readbsr = false;
|
||||
if (readMode) { this._writebsr = this._prewrite; }
|
||||
this._prewrite = readMode;
|
||||
// _debug('Bank 2 Write');
|
||||
break;
|
||||
case LOC.OFFBSR2: // 0xC082
|
||||
case LOC._OFFBSR2: // 0xC086
|
||||
this._bank1 = false;
|
||||
this._readbsr = false;
|
||||
this._writebsr = false;
|
||||
this._prewrite = false;
|
||||
// _debug('Bank 2 Off');
|
||||
break;
|
||||
case LOC.READWRBSR2: // 0xC083
|
||||
case LOC._READWRBSR2: // 0xC087
|
||||
this._bank1 = false;
|
||||
this._readbsr = true;
|
||||
if (readMode) { this._writebsr = this._prewrite; }
|
||||
this._prewrite = readMode;
|
||||
// _debug('Bank 2 Read/Write');
|
||||
break;
|
||||
case LOC.READBSR1: // 0xC088
|
||||
case LOC._READBSR1: // 0xC08c
|
||||
this._bank1 = true;
|
||||
this._readbsr = true;
|
||||
this._writebsr = false;
|
||||
this._prewrite = false;
|
||||
// _debug('Bank 1 Read');
|
||||
break;
|
||||
case LOC.WRITEBSR1: // 0xC089
|
||||
case LOC._WRITEBSR1: // 0xC08D
|
||||
this._bank1 = true;
|
||||
this._readbsr = false;
|
||||
if (readMode) { this._writebsr = this._prewrite; }
|
||||
this._prewrite = readMode;
|
||||
// _debug('Bank 1 Write');
|
||||
break;
|
||||
case LOC.OFFBSR1: // 0xC08A
|
||||
case LOC._OFFBSR1: // 0xC08E
|
||||
this._bank1 = true;
|
||||
this._readbsr = false;
|
||||
this._writebsr = false;
|
||||
this._prewrite = false;
|
||||
// _debug('Bank 1 Off');
|
||||
break;
|
||||
case LOC.READWRBSR1: // 0xC08B
|
||||
case LOC._READWRBSR1: // 0xC08F
|
||||
this._bank1 = true;
|
||||
this._readbsr = true;
|
||||
if (readMode) { this._writebsr = this._prewrite; }
|
||||
this._prewrite = readMode;
|
||||
//_debug('Bank 1 Read/Write');
|
||||
break;
|
||||
|
||||
// Status registers
|
||||
|
||||
case LOC.BSRBANK2:
|
||||
this._debug('Bank 2 Read ' + !this._bank1);
|
||||
result = !this._bank1 ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.BSRREADRAM:
|
||||
this._debug('Bank SW RAM Read ' + this._readbsr);
|
||||
result = this._readbsr ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.RAMRD: // 0xC013
|
||||
this._debug('Aux RAM Read ' + this._auxRamRead);
|
||||
result = this._auxRamRead ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RAMWRT: // 0xC014
|
||||
this._debug('Aux RAM Write ' + this._auxRamWrite);
|
||||
result = this._auxRamWrite ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.INTCXROM: // 0xC015
|
||||
// _debug('Int CX ROM ' + _intcxrom);
|
||||
result = this._intcxrom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.ALTZP: // 0xC016
|
||||
this._debug('Alt ZP ' + this._altzp);
|
||||
result = this._altzp ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.SLOTC3ROM: // 0xC017
|
||||
this._debug('Slot C3 ROM ' + this._slot3rom);
|
||||
result = this._slot3rom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC._80STORE: // 0xC018
|
||||
this._debug('80 Store ' + this._80store);
|
||||
result = this._80store ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.VERTBLANK: // 0xC019
|
||||
// result = cpu.getCycles() % 20 < 5 ? 0x80 : 0x00;
|
||||
result = (this.cpu.getCycles() < this._vbEnd) ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.RDTEXT:
|
||||
result = this.vm.isText() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDMIXED:
|
||||
result = this.vm.isMixed() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDPAGE2:
|
||||
result = this.vm.isPage2() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDHIRES:
|
||||
result = this.vm.isHires() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RD80VID:
|
||||
result = this.vm.is80Col() ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RDALTCH:
|
||||
result = this.vm.isAltChar() ? 0x80 : 0x0;
|
||||
break;
|
||||
|
||||
default:
|
||||
debug('MMU missing register ' + toHex(off));
|
||||
break;
|
||||
}
|
||||
|
||||
if (result !== undefined)
|
||||
return result;
|
||||
|
||||
result = 0;
|
||||
|
||||
this._updateBanks();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.lores1.start();
|
||||
this.lores2.start();
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
public end() {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
debug('reset');
|
||||
this._initSwitches();
|
||||
this._updateBanks();
|
||||
this.vm.reset();
|
||||
this.io.reset();
|
||||
}
|
||||
|
||||
public read(page: byte, off: byte) {
|
||||
return this._readPages[page].read(page, off);
|
||||
}
|
||||
|
||||
public write(page: byte, off: byte, val: byte) {
|
||||
this._writePages[page].write(page, off, val);
|
||||
}
|
||||
|
||||
public resetVB() {
|
||||
this._vbEnd = this.cpu.getCycles() + 1000;
|
||||
}
|
||||
/*
|
||||
public getState(): State {
|
||||
return {
|
||||
bank1: this._bank1,
|
||||
readbsr: this._readbsr,
|
||||
writebsr: this._writebsr,
|
||||
prewrite: this._prewrite,
|
||||
|
||||
intcxrom: this._intcxrom,
|
||||
slot3rom: this._slot3rom,
|
||||
intc8rom: this._intc8rom,
|
||||
|
||||
auxRamRead: this._auxRamRead,
|
||||
auxRamWrite: this._auxRamWrite,
|
||||
altzp: this._altzp,
|
||||
|
||||
_80store: this._80store,
|
||||
page2: this._page2,
|
||||
hires: this._hires,
|
||||
|
||||
mem00_01: [this.mem00_01[0].getState(), this.mem00_01[1].getState()],
|
||||
mem02_03: [this.mem02_03[0].getState(), this.mem02_03[1].getState()],
|
||||
mem0C_1F: [this.mem0C_1F[0].getState(), this.mem0C_1F[1].getState()],
|
||||
mem60_BF: [this.mem60_BF[0].getState(), this.mem60_BF[1].getState()],
|
||||
memD0_DF: [
|
||||
this.memD0_DF[0].getState(), this.memD0_DF[1].getState(),
|
||||
this.memD0_DF[2].getState(), this.memD0_DF[3].getState()
|
||||
],
|
||||
memE0_FF: [this.memE0_FF[0].getState(), this.memE0_FF[1].getState()]
|
||||
};
|
||||
}
|
||||
|
||||
public setState(state: State) {
|
||||
this._readbsr = state.readbsr;
|
||||
this._writebsr = state.writebsr;
|
||||
this._bank1 = state.bank1;
|
||||
this._prewrite = state.prewrite;
|
||||
|
||||
this._intcxrom = state.intcxrom;
|
||||
this._slot3rom = state.slot3rom;
|
||||
this._intc8rom = state.intc8rom;
|
||||
|
||||
this._auxRamRead = state.auxRamRead;
|
||||
this._auxRamWrite = state.auxRamWrite;
|
||||
this._altzp = state.altzp;
|
||||
|
||||
this._80store = state._80store;
|
||||
this._page2 = state.page2;
|
||||
this._hires = state.hires;
|
||||
|
||||
this.mem00_01[0].setState(state.mem00_01[0]);
|
||||
this.mem00_01[1].setState(state.mem00_01[1]);
|
||||
this.mem02_03[0].setState(state.mem02_03[0]);
|
||||
this.mem02_03[1].setState(state.mem02_03[1]);
|
||||
this.mem0C_1F[0].setState(state.mem0C_1F[0]);
|
||||
this.mem0C_1F[1].setState(state.mem0C_1F[1]);
|
||||
this.mem60_BF[0].setState(state.mem60_BF[0]);
|
||||
this.mem60_BF[1].setState(state.mem60_BF[1]);
|
||||
this.memD0_DF[0].setState(state.memD0_DF[0]);
|
||||
this.memD0_DF[1].setState(state.memD0_DF[1]);
|
||||
this.memD0_DF[2].setState(state.memD0_DF[2]);
|
||||
this.memD0_DF[3].setState(state.memD0_DF[3]);
|
||||
this.memE0_FF[0].setState(state.memE0_FF[0]);
|
||||
this.memE0_FF[1].setState(state.memE0_FF[1]);
|
||||
|
||||
this._updateBanks();
|
||||
}
|
||||
*/
|
||||
}
|
@ -9,22 +9,20 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
export default function Prefs()
|
||||
{
|
||||
var havePrefs = typeof window.localStorage !== 'undefined';
|
||||
const havePrefs = typeof window.localStorage !== 'undefined';
|
||||
|
||||
return {
|
||||
havePrefs: function() {
|
||||
return havePrefs;
|
||||
},
|
||||
readPref: function(name) {
|
||||
if (havePrefs)
|
||||
return window.localStorage.getItem(name);
|
||||
return null;
|
||||
},
|
||||
writePref: function(name, value) {
|
||||
if (havePrefs)
|
||||
window.localStorage.setItem(name, value);
|
||||
}
|
||||
};
|
||||
export default class Prefs {
|
||||
|
||||
havePrefs() {
|
||||
return havePrefs;
|
||||
}
|
||||
readPref(name: string): string | null {
|
||||
if (havePrefs)
|
||||
return window.localStorage.getItem(name);
|
||||
return null;
|
||||
}
|
||||
writePref(name: string, value: string) {
|
||||
if (havePrefs)
|
||||
window.localStorage.setItem(name, value);
|
||||
}
|
||||
}
|
69
js/ram.ts
69
js/ram.ts
@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { base64_decode, base64_encode } from './base64';
|
||||
import { byte } from './types';
|
||||
import { byte, memory, Memory } from './types';
|
||||
import { allocMemPages } from './util';
|
||||
|
||||
export interface State {
|
||||
@ -26,38 +26,45 @@ export interface State {
|
||||
* Represents RAM from the start page `sp` to end page `ep`. The memory
|
||||
* is addressed by `page` and `offset`.
|
||||
*/
|
||||
export default function RAM(sp: byte, ep: byte) {
|
||||
let start_page = sp;
|
||||
let end_page = ep;
|
||||
export default class RAM implements Memory {
|
||||
private start_page: byte;
|
||||
private end_page: byte;
|
||||
private mem: memory;
|
||||
|
||||
let mem = allocMemPages(ep - sp + 1);
|
||||
constructor(sp: byte, ep: byte) {
|
||||
this.start_page = sp;
|
||||
this.end_page = ep;
|
||||
|
||||
return {
|
||||
start: function () {
|
||||
return start_page;
|
||||
},
|
||||
end: function () {
|
||||
return end_page;
|
||||
},
|
||||
read: function (page: byte, offset: byte) {
|
||||
return mem[(page - start_page) << 8 | offset];
|
||||
},
|
||||
write: function (page: byte, offset: byte, val: byte) {
|
||||
mem[(page - start_page) << 8 | offset] = val;
|
||||
},
|
||||
this.mem = allocMemPages(ep - sp + 1);
|
||||
}
|
||||
|
||||
getState: function (): State {
|
||||
return {
|
||||
start: start_page,
|
||||
end: end_page,
|
||||
mem: base64_encode(mem)
|
||||
};
|
||||
},
|
||||
public start(): byte {
|
||||
return this.start_page;
|
||||
}
|
||||
|
||||
setState: function (state: State) {
|
||||
start_page = state.start;
|
||||
end_page = state.end;
|
||||
mem = base64_decode(state.mem);
|
||||
}
|
||||
};
|
||||
public end(): byte {
|
||||
return this.end_page;
|
||||
}
|
||||
|
||||
public read(page: byte, offset: byte) {
|
||||
return this.mem[(page - this.start_page) << 8 | offset];
|
||||
}
|
||||
|
||||
public write(page: byte, offset: byte, val: byte) {
|
||||
this.mem[(page - this.start_page) << 8 | offset] = val;
|
||||
}
|
||||
|
||||
public getState(): State {
|
||||
return {
|
||||
start: this.start_page,
|
||||
end: this.end_page,
|
||||
mem: base64_encode(this.mem)
|
||||
};
|
||||
}
|
||||
|
||||
public setState(state: State) {
|
||||
this.start_page = state.start;
|
||||
this.end_page = state.end;
|
||||
this.mem = base64_decode(state.mem);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
var apple2_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const apple2_charset = bytify([
|
||||
0x00,0x1c,0x22,0x2a,0x2e,0x2c,0x20,0x1e,
|
||||
0x00,0x08,0x14,0x22,0x22,0x3e,0x22,0x22,
|
||||
0x00,0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c,
|
||||
@ -255,6 +257,6 @@ var apple2_charset = [
|
||||
0x80,0x80,0x80,0xbe,0x80,0xbe,0x80,0x80,
|
||||
0x80,0x90,0x88,0x84,0x82,0x84,0x88,0x90,
|
||||
0x80,0x9c,0xa2,0x84,0x88,0x88,0x80,0x88
|
||||
];
|
||||
]);
|
||||
|
||||
export default apple2_charset;
|
2071
js/roms/apple2e.js
2071
js/roms/apple2e.js
File diff suppressed because it is too large
Load Diff
2061
js/roms/apple2e.ts
Normal file
2061
js/roms/apple2e.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
||||
/*exported apple2e_charset */
|
||||
import { bytify } from "../util";
|
||||
|
||||
var apple2e_charset = [
|
||||
/* exported apple2e_charset */
|
||||
|
||||
const apple2e_charset = bytify([
|
||||
0x1c,0x22,0x2a,0x3a,0x1a,0x02,0x3c,0x00,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x1e,0x22,0x22,0x1e,0x22,0x22,0x1e,0x00,
|
||||
@ -513,6 +515,6 @@ var apple2e_charset = [
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
];
|
||||
]);
|
||||
|
||||
export default apple2e_charset;
|
2069
js/roms/apple2enh.js
2069
js/roms/apple2enh.js
File diff suppressed because it is too large
Load Diff
2059
js/roms/apple2enh.ts
Normal file
2059
js/roms/apple2enh.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
||||
var apple2enh_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const apple2enh_charset = bytify([
|
||||
0x1c,0x22,0x2a,0x3a,0x1a,0x02,0x3c,0x00,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x1e,0x22,0x22,0x1e,0x22,0x22,0x1e,0x00,
|
||||
@ -511,6 +513,6 @@ var apple2enh_charset = [
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
];
|
||||
]);
|
||||
|
||||
export default apple2enh_charset;
|
1557
js/roms/apple2j.js
1557
js/roms/apple2j.js
File diff suppressed because it is too large
Load Diff
1547
js/roms/apple2j.ts
Normal file
1547
js/roms/apple2j.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
||||
var apple2j_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const apple2j_charset = bytify([
|
||||
0xff,0xef,0xe1,0xed,0xd5,0xfb,0xf7,0xef,
|
||||
0xff,0xfb,0xc7,0xf7,0xc1,0xf7,0xf7,0xef,
|
||||
0xff,0xff,0xd5,0xd5,0xfd,0xfd,0xfb,0xf7,
|
||||
@ -255,6 +257,6 @@ var apple2j_charset = [
|
||||
0x00,0x00,0x00,0x3e,0x00,0x3e,0x00,0x00,
|
||||
0x00,0x10,0x08,0x04,0x02,0x04,0x08,0x10,
|
||||
0x00,0x1c,0x22,0x04,0x08,0x08,0x00,0x08,
|
||||
];
|
||||
]);
|
||||
|
||||
export default apple2j_charset;
|
@ -1,4 +1,6 @@
|
||||
var apple2lc_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const apple2lc_charset = bytify([
|
||||
0x1c,0x22,0x2a,0x2a,0x2c,0x20,0x1e,0x00,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c,0x00,
|
||||
@ -255,6 +257,6 @@ var apple2lc_charset = [
|
||||
0x30,0x08,0x08,0x06,0x08,0x08,0x30,0x00,
|
||||
0x00,0x00,0x10,0x2a,0x04,0x00,0x00,0x00,
|
||||
0x2a,0x14,0x2a,0x14,0x2a,0x14,0x2a,0x00
|
||||
];
|
||||
]);
|
||||
|
||||
export default apple2lc_charset;
|
@ -1,9 +1,10 @@
|
||||
import { bytify } from "../../util";
|
||||
|
||||
/*
|
||||
* http://dreher.net/?s=projects/CFforAppleII&c=projects/CFforAppleII/downloads1.php
|
||||
*/
|
||||
|
||||
export var rom = [
|
||||
export const rom = bytify([
|
||||
0x43, 0x46, 0x46, 0x41, 0x20, 0x46, 0x69, 0x72,
|
||||
0x6d, 0x77, 0x61, 0x72, 0x65, 0x0d, 0x0d, 0x0d,
|
||||
0x53, 0x65, 0x65, 0x20, 0x3c, 0x68, 0x74, 0x74,
|
||||
@ -516,4 +517,4 @@ export var rom = [
|
||||
0x55, 0x49, 0xd4, 0x77, 0x77, 0x77, 0x77, 0xff,
|
||||
0x9d, 0x00, 0xc8, 0xa0, 0x00, 0x88, 0xf0, 0x05,
|
||||
0xdd, 0x00, 0xc8, 0xd0, 0xf8, 0x98, 0x60, 0xff
|
||||
];
|
||||
]);
|
@ -1,4 +1,6 @@
|
||||
export var P5_16 = [
|
||||
import { bytify } from "../../util";
|
||||
|
||||
export const P5_16 = bytify([
|
||||
0xa2,0x20,0xa0,0x00,0xa2,0x03,0x86,0x3c,
|
||||
0x8a,0x0a,0x24,0x3c,0xf0,0x10,0x05,0x3c,
|
||||
0x49,0xff,0x29,0x7e,0xb0,0x08,0x4a,0xd0,
|
||||
@ -31,9 +33,9 @@ export var P5_16 = [
|
||||
0xc8,0xd0,0xee,0xe6,0x27,0xe6,0x3d,0xa5,
|
||||
0x3d,0xcd,0x00,0x08,0xa6,0x2b,0x90,0xdb,
|
||||
0x4c,0x01,0x08,0x00,0x00,0x00,0x00,0x00
|
||||
];
|
||||
]);
|
||||
|
||||
export var P5_13 = [
|
||||
export const P5_13 = bytify([
|
||||
0xa2,0x20,0xa0,0x00,0xa9,0x03,0x85,0x3c,
|
||||
0x18,0x88,0x98,0x24,0x3c,0xf0,0xf5,0x26,
|
||||
0x3c,0x90,0xf8,0xc0,0xd5,0xf0,0xed,0xca,
|
||||
@ -66,4 +68,4 @@ export var P5_13 = [
|
||||
0x91,0x26,0xc8,0xe8,0xe0,0x33,0xd0,0xe4,
|
||||
0xc6,0x2a,0xd0,0xde,0xcc,0x00,0x03,0xd0,
|
||||
0x03,0x4c,0x01,0x03,0x4c,0x2d,0xff,0xff
|
||||
];
|
||||
]);
|
@ -1,4 +1,6 @@
|
||||
export var rom = [
|
||||
import { bytify } from "../../util";
|
||||
|
||||
export const rom = bytify([
|
||||
0x18,0xb0,0x38,0x48,0x8a,0x48,0x98,0x48,
|
||||
0x08,0x78,0x20,0x58,0xff,0xba,0x68,0x68,
|
||||
0x68,0x68,0xa8,0xca,0x9a,0x68,0x28,0xaa,
|
||||
@ -31,4 +33,4 @@ export var rom = [
|
||||
0xa8,0xbd,0x38,0x07,0xc5,0x24,0x68,0xb0,
|
||||
0x05,0x48,0x29,0x80,0x09,0x20,0x2c,0x58,
|
||||
0xff,0xf0,0x03,0xfe,0x38,0x07,0x70,0x84
|
||||
];
|
||||
]);
|
@ -1,4 +1,6 @@
|
||||
export var rom = [
|
||||
import { bytify } from "../../util";
|
||||
|
||||
export const rom = bytify([
|
||||
0x43,0x4f,0x50,0x59,0x52,0x49,0x47,0x48,
|
||||
0x54,0x20,0x28,0x43,0x29,0x20,0x31,0x39,
|
||||
0x38,0x36,0x2d,0x38,0x39,0x20,0x41,0x50,
|
||||
@ -1023,4 +1025,4 @@ export var rom = [
|
||||
0x05,0x09,0xaa,0x68,0xce,0x02,0x09,0xd0,
|
||||
0xf1,0xc8,0xd0,0xd7,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
|
||||
];
|
||||
]);
|
@ -1,3 +1,4 @@
|
||||
import { bytify } from "../../util";
|
||||
|
||||
/*
|
||||
$Cn01=$20
|
||||
@ -6,7 +7,7 @@
|
||||
$Cn07=$00 Smartport / $3C Disk controller
|
||||
*/
|
||||
|
||||
export var rom = [
|
||||
export const rom = bytify([
|
||||
0xa2,0x20,0xa2,0x00,0xa2,0x03,0xa2,0x00, 0x20,0x58,0xff,0xba,0xbd,0x00,0x01,0x8d,
|
||||
0xf8,0x07,0x0a,0x0a,0x0a,0x0a,0xa8,0xb9, 0x80,0xc0,0x4a,0xb0,0x11,0xa5,0x00,0xd0,
|
||||
0x0a,0xa5,0x01,0xcd,0xf8,0x07,0xd0,0x03, 0x4c,0xba,0xfa,0x4c,0x00,0xe0,0xa2,0x01,
|
||||
@ -23,4 +24,4 @@ export var rom = [
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xd7,0x53,
|
||||
];
|
||||
]);
|
@ -1,4 +1,6 @@
|
||||
export var rom = [
|
||||
import { bytify } from "../../util";
|
||||
|
||||
export const rom = bytify([
|
||||
0x08,0x78,0x28,0x2c,0x58,0xff,0x70,0x05, // 00
|
||||
0x38,0xb0,0x01,0x18,0xb8,0x08,0x78,0x48,
|
||||
0x8a,0x48,0x98,0x48,0xad,0xff,0xcf,0x20, // 10
|
||||
@ -255,4 +257,4 @@ export var rom = [
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
|
||||
];
|
||||
]);
|
@ -1,4 +1,6 @@
|
||||
export var ROM = [
|
||||
import { bytify } from "../../util";
|
||||
|
||||
export var ROM = bytify([
|
||||
0xad,0x7b,0x07,0x29,0xf8,0xc9,0x30,0xf0,
|
||||
0x21,0xa9,0x30,0x8d,0x7b,0x07,0x8d,0xfb,
|
||||
0x07,0xa9,0x00,0x8d,0xfb,0x06,0x20,0x61,
|
||||
@ -127,9 +129,9 @@ export var ROM = [
|
||||
0x05,0xe9,0x47,0x90,0xd4,0x69,0x1f,0x18,
|
||||
0x90,0xd1,0x60,0x38,0x71,0xb2,0x7b,0x00,
|
||||
0x48,0x66,0xc4,0xc2,0xc1,0xff,0xc3,0xea,
|
||||
];
|
||||
]);
|
||||
|
||||
export var VIDEO_ROM = [
|
||||
export const VIDEO_ROM = bytify([
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,
|
||||
@ -386,4 +388,4 @@ export var VIDEO_ROM = [
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x24,0x48,0x92,0x24,0x48,0x92,0x24,0x48,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
];
|
||||
]);
|
1557
js/roms/fpbasic.js
1557
js/roms/fpbasic.js
File diff suppressed because it is too large
Load Diff
1547
js/roms/fpbasic.ts
Normal file
1547
js/roms/fpbasic.ts
Normal file
File diff suppressed because it is too large
Load Diff
1555
js/roms/intbasic.js
1555
js/roms/intbasic.js
File diff suppressed because it is too large
Load Diff
1547
js/roms/intbasic.ts
Normal file
1547
js/roms/intbasic.ts
Normal file
File diff suppressed because it is too large
Load Diff
1557
js/roms/original.js
1557
js/roms/original.js
File diff suppressed because it is too large
Load Diff
1548
js/roms/original.ts
Normal file
1548
js/roms/original.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
||||
var pigfont_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const pigfont_charset = bytify([
|
||||
0x00,0x1c,0x22,0x2a,0x2e,0x20,0x1e,0x00,
|
||||
0x0c,0x12,0x21,0x3f,0x21,0x21,0x00,0x00,
|
||||
0x3e,0x11,0x1e,0x11,0x11,0x3e,0x00,0x00,
|
||||
@ -255,6 +257,6 @@ var pigfont_charset = [
|
||||
0x98,0x8c,0x9e,0x97,0xff,0xff,0xef,0x9e,
|
||||
0xbe,0xc9,0xc9,0xbe,0x9c,0xc1,0xbe,0xc1,
|
||||
0x84,0x88,0xb6,0xff,0xfc,0xfc,0xbe,0xb6,
|
||||
];
|
||||
]);
|
||||
|
||||
export default pigfont_charset;
|
@ -1,4 +1,6 @@
|
||||
var rmfont_charset = [
|
||||
import { bytify } from "../util";
|
||||
|
||||
const rmfont_charset = bytify([
|
||||
0x3c,0x42,0x59,0x55,0x55,0x39,0x02,0x3c,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x1e,0x24,0x24,0x1c,0x24,0x24,0x1e,0x00,
|
||||
@ -511,6 +513,6 @@ var rmfont_charset = [
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
];
|
||||
]);
|
||||
|
||||
export default rmfont_charset;
|
27
js/roms/rom.ts
Normal file
27
js/roms/rom.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { PageHandler } from "../cpu6502";
|
||||
import { byte, memory } from "../types";
|
||||
|
||||
export default class ROM implements PageHandler {
|
||||
|
||||
constructor(
|
||||
private readonly startPage: byte,
|
||||
private readonly endPage: byte,
|
||||
private readonly rom: memory) {
|
||||
const expectedLength = (endPage-startPage+1) * 256;
|
||||
if (rom.length != expectedLength) {
|
||||
throw Error(`rom does not have the correct length: expected ${expectedLength} was ${rom.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.startPage;
|
||||
}
|
||||
end() {
|
||||
return this.endPage;
|
||||
}
|
||||
read(page: byte, off: byte) {
|
||||
return this.rom[(page - this.startPage) << 8 | off];
|
||||
}
|
||||
write() {
|
||||
}
|
||||
}
|
19
js/types.ts
19
js/types.ts
@ -5,9 +5,21 @@ export type byte = number;
|
||||
/** A word (0..65535). This is not enforced by the compiler. */
|
||||
export type word = number;
|
||||
|
||||
/** A region of memory. */
|
||||
/** A raw region of memory. */
|
||||
export type memory = number[] | Uint8Array;
|
||||
|
||||
/** A mapped region of memory. */
|
||||
export interface Memory {
|
||||
/** Start page. */
|
||||
start(): byte;
|
||||
/** End page, inclusive. */
|
||||
end(): byte;
|
||||
/** Read a byte. */
|
||||
read(page: byte, offset: byte): byte;
|
||||
/** Write a byte. */
|
||||
write(page: byte, offset: byte, value: byte): void;
|
||||
}
|
||||
|
||||
export type DiskFormat = '2mg' | 'd13' | 'do' | 'dsk' | 'hdv' | 'po' | 'nib' | 'woz';
|
||||
|
||||
export interface Drive {
|
||||
@ -25,3 +37,8 @@ export interface DiskIIDrive extends Drive {
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
export interface Restorable<T> {
|
||||
getState(): T;
|
||||
setState(state: T): void;
|
||||
}
|
@ -628,9 +628,9 @@ function _keydown(evt) {
|
||||
} else if (evt.keyCode === 114) { // F3
|
||||
io.keyDown(0x1b);
|
||||
} else if (evt.keyCode === 117) { // F6 Quick Save
|
||||
_apple2.saveState();
|
||||
_apple2.getState();
|
||||
} else if (evt.keyCode === 120) { // F9 Quick Restore
|
||||
_apple2.restoreState();
|
||||
_apple2.setState();
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
} else if (evt.keyCode == 20) { // Caps lock
|
||||
|
24
test/js/rom.test.ts
Normal file
24
test/js/rom.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type ROM from '../../js/roms/rom';
|
||||
import OriginalROM from '../../js/roms/original';
|
||||
import IntegerROM from '../../js/roms/intbasic';
|
||||
import FPBasicROM from '../../js/roms/fpbasic';
|
||||
import Apple2eROM from '../../js/roms/apple2e';
|
||||
import Apple2enhROM from '../../js/roms/apple2enh';
|
||||
import Apple2jROM from '../../js/roms/apple2j';
|
||||
|
||||
const roms: { [name: string]: { new(): ROM } } = {
|
||||
'original': OriginalROM,
|
||||
'integer': IntegerROM,
|
||||
'fpbasic': FPBasicROM,
|
||||
'apple2e': Apple2eROM,
|
||||
'apple2enh': Apple2enhROM,
|
||||
'apple2j': Apple2jROM,
|
||||
};
|
||||
|
||||
for (let rom of Object.keys(roms)) {
|
||||
describe(`${rom}`, () => {
|
||||
it('is constructable', () => {
|
||||
new roms[rom]();
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user