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:
Ian Flanigan 2020-11-24 17:48:14 +01:00 committed by GitHub
parent e1b807ba9e
commit b80436d99c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 13560 additions and 13387 deletions

View File

@ -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
View 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;
}
}

View File

@ -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
View 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();
}
}

File diff suppressed because it is too large Load Diff

1202
js/canvas.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 = {

View File

@ -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;
}

View File

@ -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
View File

@ -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
View 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();
}
*/
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

2061
js/roms/apple2e.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

File diff suppressed because it is too large Load Diff

2059
js/roms/apple2enh.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

File diff suppressed because it is too large Load Diff

1547
js/roms/apple2j.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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;

View File

@ -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
];
]);

View File

@ -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
];
]);

View File

@ -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
];
]);

View File

@ -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
];
]);

View File

@ -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,
];
]);

View File

@ -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
];
]);

View File

@ -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,
];
]);

File diff suppressed because it is too large Load Diff

1547
js/roms/fpbasic.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1547
js/roms/intbasic.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1548
js/roms/original.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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
View 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() {
}
}

View File

@ -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;
}

View File

@ -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
View 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]();
});
});
}