mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Convert cards/disk2.js
to Typescript (#54)
* Convert `cards/disk2.js` to Typescript This is mostly a straightforward conversion of `cards/disk2.js` to Typescript, with the following exceptions: * `setState()` did not restore the drive light state correctly because the callback was called with the old `on` value. * `setPhase()` did not work for WOZ images. * `getBinary()` did not work for `nib` files. * `getBase64()` did not work for `nib` files and maybe didn't work right at all. Even with these fixes, local storage still doesn't work correctly. I have also added several TODOs where methods don't support WOZ disks. * Convert most uses of `memory` to `Uint8Array` There are many places in the existing code where we use `Uint8Array` directly. This change merely makes the `memory` type equivalent to `Uint8Array`. This change also changes most ROM data to be read-only in Typescript to ensure that it is not modified by mistake. This can't be done just by applying `as const` to the declaration because `Uint8Array`s are can not be expressed as literals. Instead, we create a new type, `ReadonlyUint8Array` that drops the mutation methods and makes indexed access read-only. See https://www.growingwiththeweb.com/2020/10/typescript-readonly-typed-arrays.html for details. * Tighten types and document `disk2.ts` While trying to understand the Disk ][ emulation, I tighted the types and documented the parts that I could, including references to other sources, like _Understanding the Apple //e_ by Jim Sather. The one functional change is the addition of the P6 ROM of DOS 3.2 and earlier. This is automatically selected if the card is initialized for 13 sector disks.
This commit is contained in:
parent
dc13b6a59a
commit
910238bf63
@ -119,5 +119,5 @@ export function base64_decode(data: string | null | undefined): memory | undefin
|
||||
}
|
||||
} while (i < data.length);
|
||||
|
||||
return tmp_arr;
|
||||
return new Uint8Array(tmp_arr);
|
||||
}
|
||||
|
@ -1,690 +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 { base64_decode, base64_encode } from '../base64';
|
||||
import { debug, toHex } from '../util';
|
||||
import { jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||
|
||||
import { P5_16, P5_13 } from '../roms/cards/disk2';
|
||||
|
||||
import _2MG from '../formats/2mg';
|
||||
import D13 from '../formats/d13';
|
||||
import DOS from '../formats/do';
|
||||
import ProDOS from '../formats/po';
|
||||
import Woz from '../formats/woz';
|
||||
import Nibble from '../formats/nib';
|
||||
|
||||
export const DISK_TYPES = [
|
||||
'2mg',
|
||||
'd13',
|
||||
'do',
|
||||
'dsk',
|
||||
'hdv',
|
||||
'po',
|
||||
'nib',
|
||||
'woz'
|
||||
];
|
||||
|
||||
export default function DiskII(io, callbacks, sectors = 16)
|
||||
{
|
||||
var _drives = [
|
||||
{ // Drive 1
|
||||
format: 'dsk',
|
||||
volume: 254,
|
||||
tracks: [],
|
||||
trackMap: null,
|
||||
rawTracks: null,
|
||||
track: 0,
|
||||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
},
|
||||
{ // Drive 2
|
||||
format: 'dsk',
|
||||
volume: 254,
|
||||
tracks: [],
|
||||
trackMap: null,
|
||||
rawTracks: null,
|
||||
track: 0,
|
||||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
}];
|
||||
|
||||
var _skip = 0;
|
||||
var _bus = 0;
|
||||
var _latch = 0;
|
||||
var _offTimeout = null;
|
||||
var _q6 = 0;
|
||||
var _q7 = 0;
|
||||
var _writeMode = false; // q7
|
||||
var _on = false;
|
||||
var _drive = 1;
|
||||
var _cur = _drives[_drive - 1];
|
||||
|
||||
var LOC = {
|
||||
// Disk II Stuff
|
||||
PHASE0OFF: 0x80,
|
||||
PHASE0ON: 0x81,
|
||||
PHASE1OFF: 0x82,
|
||||
PHASE1ON: 0x83,
|
||||
PHASE2OFF: 0x84,
|
||||
PHASE2ON: 0x85,
|
||||
PHASE3OFF: 0x86,
|
||||
PHASE3ON: 0x87,
|
||||
|
||||
DRIVEOFF: 0x88,
|
||||
DRIVEON: 0x89,
|
||||
DRIVE1: 0x8A,
|
||||
DRIVE2: 0x8B,
|
||||
DRIVEREAD: 0x8C, // Q6L
|
||||
DRIVEWRITE: 0x8D, // Q6H
|
||||
DRIVEREADMODE: 0x8E, // Q7L
|
||||
DRIVEWRITEMODE: 0x8F // Q7H
|
||||
};
|
||||
|
||||
|
||||
// CODE OPERATION BEFORE AFTER
|
||||
// 0 CLR XXXXXXXX 00000000
|
||||
// 8 NOP ABCDEFGH ABCDEFGH
|
||||
// 9 SL0 ABCDEFGH BCDEFGH0
|
||||
// A SR (write protected) ABCDEFGH 11111111
|
||||
// (not write protected) ABCDEFGH 0ABCDEFG
|
||||
// B LOAD XXXXXXXX YYYYYYYY
|
||||
// D SL1 ABCDEFGH BCDEFGH1
|
||||
|
||||
// Q7 Read/Write
|
||||
// Q6 Shift/Load
|
||||
|
||||
var _P6 = [
|
||||
// Q7 L (Read) Q7 H (Write)
|
||||
// Q6 L Q6 H Q6 L (Shift) Q6 H (Load)
|
||||
// QA L QA H QA L QA H QA L QA H QA L QA H
|
||||
//1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
|
||||
0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0
|
||||
0x2D, 0x2D, 0x38, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // 1
|
||||
0xD8, 0x38, 0x08, 0x28, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // 2
|
||||
0xD8, 0x48, 0x48, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // 3
|
||||
0xD8, 0x58, 0xD8, 0x58, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // 4
|
||||
0xD8, 0x68, 0xD8, 0x68, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // 5
|
||||
0xD8, 0x78, 0xD8, 0x78, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // 6
|
||||
0xD8, 0x88, 0xD8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, // 7
|
||||
0xD8, 0x98, 0xD8, 0x98, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // 8
|
||||
0xD8, 0x29, 0xD8, 0xA8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // 9
|
||||
0xCD, 0xBD, 0xD8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // A
|
||||
0xD9, 0x59, 0xD8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // B
|
||||
0xD9, 0xD9, 0xD8, 0xA0, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // C
|
||||
0xD8, 0x08, 0xE8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // D
|
||||
0xFD, 0xFD, 0xF8, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // E
|
||||
0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F
|
||||
];
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _init() {
|
||||
debug('Disk ][');
|
||||
}
|
||||
|
||||
var _clock = 0;
|
||||
var _lastCycles = io.cycles();
|
||||
var _state = 0;
|
||||
var _zeros = 0;
|
||||
|
||||
function _moveHead() {
|
||||
if (!_cur.rawTracks) {
|
||||
return;
|
||||
}
|
||||
var track = _cur.rawTracks[_cur.trackMap[_cur.track]] || [0];
|
||||
|
||||
var cycles = io.cycles();
|
||||
var workCycles = (cycles - _lastCycles) * 2;
|
||||
_lastCycles = cycles;
|
||||
|
||||
while (workCycles-- > 0) {
|
||||
var pulse = 0;
|
||||
if (_clock == 4) {
|
||||
pulse = track[_cur.head];
|
||||
if (!pulse) {
|
||||
if (++_zeros > 2) {
|
||||
pulse = Math.random() > 0.5 ? 1 : 0;
|
||||
}
|
||||
} else {
|
||||
_zeros = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var idx = 0;
|
||||
idx |= pulse ? 0x00 : 0x01;
|
||||
idx |= _latch & 0x80 ? 0x02 : 0x00;
|
||||
idx |= _q6 ? 0x04 : 0x00;
|
||||
idx |= _q7 ? 0x08 : 0x00;
|
||||
idx |= _state << 4;
|
||||
|
||||
var command = _P6[idx];
|
||||
|
||||
if (_on && _q7) {
|
||||
debug('clock:', _clock, 'command:', toHex(command), 'q6:', _q6);
|
||||
}
|
||||
|
||||
switch (command & 0xf) {
|
||||
case 0x0: // CLR
|
||||
_latch = 0;
|
||||
break;
|
||||
case 0x8: // NOP
|
||||
break;
|
||||
case 0x9: // SL0
|
||||
_latch = (_latch << 1) & 0xff;
|
||||
break;
|
||||
case 0xA: // SR
|
||||
_latch >>= 1;
|
||||
if (_cur.readOnly) {
|
||||
_latch |= 0x80;
|
||||
}
|
||||
break;
|
||||
case 0xB: // LD
|
||||
_latch = _bus;
|
||||
debug('Loading', toHex(_latch), 'from bus');
|
||||
break;
|
||||
case 0xD: // SL1
|
||||
_latch = ((_latch << 1) | 0x01) & 0xff;
|
||||
break;
|
||||
}
|
||||
_state = command >> 4;
|
||||
|
||||
if (_clock == 4) {
|
||||
if (_on) {
|
||||
if (_q7) {
|
||||
track[_cur.head] = _state & 0x8 ? 0x01 : 0x00;
|
||||
debug('Wrote', _state & 0x8 ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
if (++_cur.head >= track.length) {
|
||||
_cur.head = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++_clock > 7) {
|
||||
_clock = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _readWriteNext() {
|
||||
if (_skip || _writeMode) {
|
||||
var track = _cur.tracks[_cur.track >> 2];
|
||||
if (track && track.length) {
|
||||
if (_cur.head >= track.length) {
|
||||
_cur.head = 0;
|
||||
}
|
||||
|
||||
if (_writeMode) {
|
||||
if (!_cur.readOnly) {
|
||||
track[_cur.head] = _bus;
|
||||
if (!_cur.dirty) {
|
||||
_updateDirty(_drive, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_latch = track[_cur.head];
|
||||
}
|
||||
|
||||
++_cur.head;
|
||||
}
|
||||
} else {
|
||||
_latch = 0;
|
||||
}
|
||||
_skip = (++_skip % 2);
|
||||
}
|
||||
|
||||
var _phase_delta = [
|
||||
[ 0, 1, 2,-1],
|
||||
[-1, 0, 1, 2],
|
||||
[-2,-1, 0, 1],
|
||||
[ 1,-2,-1, 0]
|
||||
];
|
||||
|
||||
var _q = [false, false, false, false]; // q0-3
|
||||
|
||||
function setPhase(phase, on) {
|
||||
_debug('phase ' + phase + (on ? ' on' : ' off'));
|
||||
if (_cur.rawTracks) {
|
||||
if (on) {
|
||||
var delta = _phase_delta[_cur.phase][phase] * 2;
|
||||
_cur.track += delta;
|
||||
_cur.phase = phase;
|
||||
} else {
|
||||
// foo
|
||||
}
|
||||
} else {
|
||||
if (on) {
|
||||
_cur.track += _phase_delta[_cur.phase][phase] * 2;
|
||||
_cur.phase = phase;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cur.track > _cur.tracks.length * 4 - 1) {
|
||||
_cur.track = _cur.tracks.length * 4 - 1;
|
||||
}
|
||||
if (_cur.track < 0x0) {
|
||||
_cur.track = 0x0;
|
||||
}
|
||||
|
||||
// debug(
|
||||
// 'Drive', _drive, 'track', toHex(_cur.track >> 2) + '.' + (_cur.track & 0x3),
|
||||
// '(' + toHex(_cur.track) + ')',
|
||||
// '[' + phase + ':' + (on ? 'on' : 'off') + ']');
|
||||
|
||||
_q[phase] = on;
|
||||
}
|
||||
|
||||
function _access(off, val) {
|
||||
var result = 0;
|
||||
var readMode = val === undefined;
|
||||
|
||||
switch (off & 0x8f) {
|
||||
case LOC.PHASE0OFF: // 0x00
|
||||
setPhase(0, false);
|
||||
break;
|
||||
case LOC.PHASE0ON: // 0x01
|
||||
setPhase(0, true);
|
||||
break;
|
||||
case LOC.PHASE1OFF: // 0x02
|
||||
setPhase(1, false);
|
||||
break;
|
||||
case LOC.PHASE1ON: // 0x03
|
||||
setPhase(1, true);
|
||||
break;
|
||||
case LOC.PHASE2OFF: // 0x04
|
||||
setPhase(2, false);
|
||||
break;
|
||||
case LOC.PHASE2ON: // 0x05
|
||||
setPhase(2, true);
|
||||
break;
|
||||
case LOC.PHASE3OFF: // 0x06
|
||||
setPhase(3, false);
|
||||
break;
|
||||
case LOC.PHASE3ON: // 0x07
|
||||
setPhase(3, true);
|
||||
break;
|
||||
|
||||
case LOC.DRIVEOFF: // 0x08
|
||||
if (!_offTimeout) {
|
||||
if (_on) {
|
||||
_offTimeout = window.setTimeout(function() {
|
||||
_debug('Drive Off');
|
||||
_on = false;
|
||||
if (callbacks.driveLight) { callbacks.driveLight(_drive, false); }
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LOC.DRIVEON: // 0x09
|
||||
if (_offTimeout) {
|
||||
window.clearTimeout(_offTimeout);
|
||||
_offTimeout = null;
|
||||
}
|
||||
if (!_on) {
|
||||
_debug('Drive On');
|
||||
_on = true;
|
||||
_lastCycles = io.cycles();
|
||||
if (callbacks.driveLight) { callbacks.driveLight(_drive, true); }
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVE1: // 0x0a
|
||||
_debug('Disk 1');
|
||||
_drive = 1;
|
||||
_cur = _drives[_drive - 1];
|
||||
if (_on && callbacks.driveLight) {
|
||||
callbacks.driveLight(2, false);
|
||||
callbacks.driveLight(1, true);
|
||||
}
|
||||
break;
|
||||
case LOC.DRIVE2: // 0x0b
|
||||
_debug('Disk 2');
|
||||
_drive = 2;
|
||||
_cur = _drives[_drive - 1];
|
||||
if (_on && callbacks.driveLight) {
|
||||
callbacks.driveLight(1, false);
|
||||
callbacks.driveLight(2, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEREAD: // 0x0c (Q6L) Shift
|
||||
_q6 = 0;
|
||||
if (_writeMode) {
|
||||
_debug('clearing _q6/SHIFT');
|
||||
}
|
||||
if (!_cur.rawTracks) {
|
||||
_readWriteNext();
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEWRITE: // 0x0d (Q6H) LOAD
|
||||
_q6 = 1;
|
||||
if (_writeMode) {
|
||||
_debug('setting _q6/LOAD');
|
||||
}
|
||||
if (!_cur.rawTracks) {
|
||||
if (readMode && !_writeMode) {
|
||||
if (_cur.readOnly) {
|
||||
_latch = 0xff;
|
||||
_debug('Setting readOnly');
|
||||
} else {
|
||||
_latch = _latch >> 1;
|
||||
_debug('Clearing readOnly');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEREADMODE: // 0x0e (Q7L)
|
||||
_debug('Read Mode');
|
||||
_q7 = 0;
|
||||
_writeMode = false;
|
||||
break;
|
||||
case LOC.DRIVEWRITEMODE: // 0x0f (Q7H)
|
||||
_debug('Write Mode');
|
||||
_q7 = 1;
|
||||
_writeMode = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_moveHead();
|
||||
|
||||
if (readMode) {
|
||||
if ((off & 0x01) === 0) {
|
||||
result = _latch;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
_bus = val;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function _updateDirty(drive, dirty) {
|
||||
_drives[drive - 1].dirty = dirty;
|
||||
if (callbacks.dirty) { callbacks.dirty(_drive, dirty); }
|
||||
}
|
||||
|
||||
var _P5 = sectors == 16 ? P5_16 : P5_13;
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
ioSwitch: function disk2_ioSwitch(off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
|
||||
read: function disk2_read(page, off) {
|
||||
return _P5[off];
|
||||
},
|
||||
|
||||
write: function disk2_write() {},
|
||||
|
||||
reset: function disk2_reset() {
|
||||
if (_on) {
|
||||
callbacks.driveLight(_drive, false);
|
||||
_writeMode = false;
|
||||
_on = false;
|
||||
_drive = 1;
|
||||
_cur = _drives[_drive - 1];
|
||||
}
|
||||
for (var idx = 0; idx < 4; idx++) {
|
||||
_q[idx] = false;
|
||||
}
|
||||
},
|
||||
|
||||
tick: function disk2_tick() {
|
||||
_moveHead();
|
||||
},
|
||||
|
||||
getState: function disk2_getState() {
|
||||
function getDriveState(drive) {
|
||||
var result = {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
tracks: [],
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
for (var idx = 0; idx < drive.tracks.length; idx++) {
|
||||
result.tracks.push(base64_encode(drive.tracks[idx]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
var result = {
|
||||
drives: [],
|
||||
skip: _skip,
|
||||
latch: _latch,
|
||||
writeMode: _writeMode,
|
||||
on: _on,
|
||||
drive: _drive
|
||||
};
|
||||
_drives.forEach(function(drive, idx) {
|
||||
result.drives[idx] = getDriveState(drive);
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
setState: function disk2_setState(state) {
|
||||
function setDriveState(state) {
|
||||
var result = {
|
||||
format: state.format,
|
||||
volume: state.volume,
|
||||
tracks: [],
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
readOnly: state.readOnly,
|
||||
dirty: state.dirty
|
||||
};
|
||||
for (var idx = 0; idx < state.tracks.length; idx++) {
|
||||
result.tracks.push(base64_decode(state.tracks[idx]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
state.drives.forEach(function(drive, idx) {
|
||||
_drives[idx] = setDriveState(drive);
|
||||
callbacks.driveLight(idx, _drive.on);
|
||||
callbacks.dirty(idx, _drive.dirty);
|
||||
});
|
||||
_skip = state.skip;
|
||||
_latch = state.latch;
|
||||
_writeMode = state.writeMode;
|
||||
_on = state.on;
|
||||
_drive = state.drive;
|
||||
_cur = _drives[_drive - 1];
|
||||
},
|
||||
|
||||
getMetadata: function disk_getMetadata(driveNo) {
|
||||
var drive = _drives[driveNo - 1];
|
||||
if (drive.tracks.length) {
|
||||
return {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
rwts: function disk2_rwts(disk, track, sector) {
|
||||
var cur = _drives[disk - 1];
|
||||
return readSector(cur, track, sector);
|
||||
},
|
||||
|
||||
setDisk: function disk2_setDisk(drive, disk) {
|
||||
var fmt = disk.type, readOnly = disk.readOnly;
|
||||
|
||||
var data, t, s;
|
||||
if (disk.encoding == 'base64') {
|
||||
data = [];
|
||||
for (t = 0; t < disk.data.length; t++) {
|
||||
if (fmt == 'nib') {
|
||||
data[t] = base64_decode(disk.data[t]);
|
||||
} else {
|
||||
data[t] = [];
|
||||
for (s = 0; s < disk.data[t].length; s++) {
|
||||
data[t][s] = base64_decode(disk.data[t][s]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = disk.data;
|
||||
}
|
||||
var cur = _drives[drive - 1];
|
||||
|
||||
// var v = (fmt === 'dsk' ? data[0x11][0x00][0x06] : 0xfe);
|
||||
// if (v == 0x00) {
|
||||
var volume = disk.volume || 0xfe;
|
||||
// }
|
||||
|
||||
var options = {
|
||||
volume,
|
||||
readOnly,
|
||||
name,
|
||||
data
|
||||
};
|
||||
|
||||
switch (fmt) {
|
||||
case 'd13':
|
||||
disk = new D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
disk = new DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
disk = new Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
disk = new ProDOS(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(cur, disk);
|
||||
_updateDirty(_drive, false);
|
||||
},
|
||||
|
||||
getJSON: function disk2_getJSON(drive, pretty) {
|
||||
var cur = _drives[drive - 1];
|
||||
return jsonEncode(cur, pretty);
|
||||
},
|
||||
|
||||
setJSON: function disk2_setJSON(drive, data) {
|
||||
var cur = _drives[drive - 1];
|
||||
Object.assign(cur, jsonDecode(data));
|
||||
return true;
|
||||
},
|
||||
|
||||
setBinary: function disk2_setBinary(drive, name, fmt, rawData) {
|
||||
var disk;
|
||||
var cur = _drives[drive - 1];
|
||||
var readOnly = false;
|
||||
var volume = 254;
|
||||
var options = {
|
||||
name,
|
||||
rawData,
|
||||
readOnly,
|
||||
volume
|
||||
};
|
||||
|
||||
switch (fmt) {
|
||||
case '2mg':
|
||||
disk = new _2MG(options);
|
||||
break;
|
||||
case 'd13':
|
||||
disk = new D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
disk = new DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
disk = new Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
disk = new ProDOS(options);
|
||||
break;
|
||||
case 'woz':
|
||||
disk = new Woz(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(cur, disk);
|
||||
_updateDirty(drive, true);
|
||||
return true;
|
||||
},
|
||||
|
||||
getBinary: function disk2_getBinary(drive) {
|
||||
var cur = _drives[drive - 1];
|
||||
var len = (16 * cur.tracks.length * 256);
|
||||
var data = new Uint8Array(len);
|
||||
var idx = 0;
|
||||
|
||||
for (var t = 0; t < cur.tracks.length; t++) {
|
||||
if (cur.format === 'nib') {
|
||||
data[idx++] = cur.tracks[t];
|
||||
} else {
|
||||
for (var s = 0; s < 0x10; s++) {
|
||||
var sector = readSector(cur, t, s);
|
||||
for (var b = 0; b < 256; b++) {
|
||||
data[idx++] = sector[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
getBase64: function disk2_getBase64(drive) {
|
||||
var cur = _drives[drive - 1];
|
||||
var data = [];
|
||||
for (var t = 0; t < cur.tracks.length; t++) {
|
||||
data[t] = [];
|
||||
if (cur.format === 'nib') {
|
||||
data += base64_encode(cur.tracks[t]);
|
||||
} else {
|
||||
for (var s = 0; s < 0x10; s++) {
|
||||
data += base64_encode(readSector(cur, t, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
}
|
884
js/cards/disk2.ts
Normal file
884
js/cards/disk2.ts
Normal file
@ -0,0 +1,884 @@
|
||||
/* 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 { base64_decode, base64_encode } from '../base64';
|
||||
import { bit, byte, DiskFormat, MemberOf, memory, nibble, rom } from '../types';
|
||||
import { debug, toHex } from '../util';
|
||||
import { Disk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||
|
||||
import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2';
|
||||
|
||||
import _2MG from '../formats/2mg';
|
||||
import D13 from '../formats/d13';
|
||||
import DOS from '../formats/do';
|
||||
import ProDOS from '../formats/po';
|
||||
import Woz from '../formats/woz';
|
||||
import Nibble from '../formats/nib';
|
||||
import Apple2IO from '../apple2io';
|
||||
|
||||
|
||||
/** Softswitch locations */
|
||||
const LOC = {
|
||||
// Disk II Controller Commands
|
||||
// See Understanding the Apple IIe, Table 9.1
|
||||
PHASE0OFF: 0x80, // Q0L: Phase 0 OFF
|
||||
PHASE0ON: 0x81, // Q0H: Phase 0 ON
|
||||
PHASE1OFF: 0x82, // Q1L: Phase 1 OFF
|
||||
PHASE1ON: 0x83, // Q1H: Phase 1 ON
|
||||
PHASE2OFF: 0x84, // Q2L: Phase 2 OFF
|
||||
PHASE2ON: 0x85, // Q2H: Phase 2 ON
|
||||
PHASE3OFF: 0x86, // Q3L: Phase 3 OFF
|
||||
PHASE3ON: 0x87, // Q3H: Phase 3 ON
|
||||
|
||||
DRIVEOFF: 0x88, // Q4L: Drives OFF
|
||||
DRIVEON: 0x89, // Q4H: Selected drive ON
|
||||
DRIVE1: 0x8A, // Q5L: Select drive 1
|
||||
DRIVE2: 0x8B, // Q5H: Select drive 2
|
||||
DRIVEREAD: 0x8C, // Q6L: Shift while writing; read data
|
||||
DRIVEWRITE: 0x8D, // Q6H: Load while writing; read write protect
|
||||
DRIVEREADMODE: 0x8E, // Q7L: Read
|
||||
DRIVEWRITEMODE: 0x8F // Q7H: Write
|
||||
} as const;
|
||||
|
||||
|
||||
/** Logic state sequencer ROM */
|
||||
// See Understanding the Apple IIe, Table 9.3 Logic State Sequencer Commands
|
||||
// CODE OPERATION BEFORE AFTER
|
||||
// 0 CLR XXXXXXXX 00000000
|
||||
// 8 NOP ABCDEFGH ABCDEFGH
|
||||
// 9 SL0 ABCDEFGH BCDEFGH0
|
||||
// A SR (write protected) ABCDEFGH 11111111
|
||||
// (not write protected) ABCDEFGH 0ABCDEFG
|
||||
// B LOAD XXXXXXXX YYYYYYYY
|
||||
// D SL1 ABCDEFGH BCDEFGH1
|
||||
|
||||
const SEQUENCER_ROM_13 = [
|
||||
// See Understanding the Apple IIe, Figure 9.10 The DOS 3.2 Logic State Sequencer
|
||||
// Note that the column order here is NOT the same as in Figure 9.10 for Q7 H (Write).
|
||||
//
|
||||
// Q7 L (Read) Q7 H (Write)
|
||||
// Q6 L (Shift) Q6 H (Load) Q6 L (Shift) Q6 H (Load)
|
||||
// QA L QA H QA L QA H QA L QA H QA L QA H
|
||||
// 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
|
||||
0xD8, 0x18, 0x18, 0x08, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0
|
||||
0xD8, 0x2D, 0x28, 0x28, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // 1
|
||||
0xD8, 0x38, 0x38, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // 2
|
||||
0xD8, 0x48, 0xD8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // 3
|
||||
0xD8, 0x58, 0xD8, 0x58, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // 4
|
||||
0xD8, 0x68, 0xD8, 0x68, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // 5
|
||||
0xD8, 0x78, 0xD8, 0x78, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // 6
|
||||
0xD8, 0x88, 0xD8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, // 7
|
||||
0xD8, 0x98, 0xD8, 0x98, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // 8
|
||||
0xD8, 0x09, 0xD8, 0xA8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // 9
|
||||
0xCD, 0xBD, 0xD8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // A
|
||||
0xD9, 0x39, 0xD8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // B
|
||||
0xD9, 0xD9, 0xD8, 0xA0, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // C
|
||||
0x1D, 0x0D, 0xE8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // D
|
||||
0xFD, 0xFD, 0xF8, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // E
|
||||
0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F
|
||||
] as const;
|
||||
|
||||
const SEQUENCER_ROM_16 = [
|
||||
// See Understanding the Apple IIe, Figure 9.11 The DOS 3.3 Logic State Sequencer
|
||||
// Note that the column order here is NOT the same as in Figure 9.11 for Q7 H (Write).
|
||||
//
|
||||
// Q7 L (Read) Q7 H (Write)
|
||||
// Q6 L (Shift) Q6 H (Load) Q6 L (Shift) Q6 H (Load)
|
||||
// QA L QA H QA L QA H QA L QA H QA L QA H
|
||||
// 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
|
||||
0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0
|
||||
0x2D, 0x2D, 0x38, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // 1
|
||||
0xD8, 0x38, 0x08, 0x28, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // 2
|
||||
0xD8, 0x48, 0x48, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // 3
|
||||
0xD8, 0x58, 0xD8, 0x58, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // 4
|
||||
0xD8, 0x68, 0xD8, 0x68, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // 5
|
||||
0xD8, 0x78, 0xD8, 0x78, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // 6
|
||||
0xD8, 0x88, 0xD8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, // 7
|
||||
0xD8, 0x98, 0xD8, 0x98, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // 8
|
||||
0xD8, 0x29, 0xD8, 0xA8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // 9
|
||||
0xCD, 0xBD, 0xD8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // A
|
||||
0xD9, 0x59, 0xD8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // B
|
||||
0xD9, 0xD9, 0xD8, 0xA0, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // C
|
||||
0xD8, 0x08, 0xE8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // D
|
||||
0xFD, 0xFD, 0xF8, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // E
|
||||
0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F
|
||||
] as const;
|
||||
|
||||
type LssClockCycle = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
type Phase = 0 | 1 | 2 | 3;
|
||||
|
||||
/**
|
||||
* How far the head moves, in quarter tracks, when in phase X and phase Y is
|
||||
* activated. For example, if in phase 0 (top row), turning on phase 3 would
|
||||
* step backwards a quarter track while turning on phase 2 would step forwards
|
||||
* a half track.
|
||||
*
|
||||
* Note that this emulation is highly simplified as it only takes into account
|
||||
* the order that coils are powered on and ignores when they are powered off.
|
||||
* The actual hardware allows for multiple coils to be powered at the same time
|
||||
* providing different levels of torque on the head arm. Along with that, the
|
||||
* RWTS uses a complex delay system to drive the coils faster based on expected
|
||||
* head momentum.
|
||||
*
|
||||
* Examining the https://computerhistory.org/blog/apple-ii-dos-source-code/,
|
||||
* one finds that the SEEK routine on line 4831 of `appdos31.lst`. It uses
|
||||
* `ONTABLE` and `OFFTABLE` (each 12 bytes) to know exactly how many
|
||||
* microseconds to power on/off each coil as the head accelerates. At the end,
|
||||
* the final coil is left powered on 9.5 milliseconds to ensure the head has
|
||||
* settled.
|
||||
*
|
||||
* https://embeddedmicro.weebly.com/apple-2iie.html shows traces of the boot
|
||||
* seek (which is slightly different) and a regular seek.
|
||||
*/
|
||||
const PHASE_DELTA = [
|
||||
[0, 1, 2, -1],
|
||||
[-1, 0, 1, 2],
|
||||
[-2, -1, 0, 1],
|
||||
[1, -2, -1, 0]
|
||||
] as const;
|
||||
|
||||
const DRIVE_NUMBERS = [1, 2] as const;
|
||||
type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
|
||||
|
||||
interface Callbacks {
|
||||
driveLight: (drive: DriveNumber, on: boolean) => void;
|
||||
dirty: (drive: DriveNumber, dirty: boolean) => void;
|
||||
}
|
||||
|
||||
/** Common information for Nibble and WOZ disks. */
|
||||
interface BaseDrive {
|
||||
/** Current disk format. */
|
||||
format: DiskFormat,
|
||||
/** Current disk volume number. */
|
||||
volume: byte,
|
||||
/** Quarter track position of read/write head. */
|
||||
track: byte,
|
||||
/** Position of the head on the track. */
|
||||
head: byte,
|
||||
/** Current active coil in the head stepper motor. */
|
||||
phase: Phase,
|
||||
/** Whether the drive write protect is on. */
|
||||
readOnly: boolean,
|
||||
/** Whether the drive has been written to since it was loaded. */
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
/** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */
|
||||
interface WozDrive extends BaseDrive {
|
||||
/** Maps quarter tracks to data in rawTracks; `0xFF` = random garbage. */
|
||||
trackMap: byte[];
|
||||
/** Unique track bitstreams. The index is arbitrary; it is NOT the track number. */
|
||||
rawTracks: bit[][];
|
||||
}
|
||||
|
||||
/** Nibble format track data. */
|
||||
interface NibbleDrive extends BaseDrive {
|
||||
/** Nibble data. The index is the track number. */
|
||||
tracks: memory[];
|
||||
}
|
||||
|
||||
type Drive = WozDrive | NibbleDrive;
|
||||
|
||||
function isNibbleDrive(drive: Drive): drive is NibbleDrive {
|
||||
return 'tracks' in drive;
|
||||
}
|
||||
|
||||
// Does not support WOZ disks
|
||||
interface DriveState {
|
||||
format: DiskFormat,
|
||||
volume: byte,
|
||||
tracks: string[],
|
||||
track: byte,
|
||||
head: byte,
|
||||
phase: Phase,
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
interface State {
|
||||
drives: DriveState[];
|
||||
skip: number;
|
||||
latch: number;
|
||||
writeMode: boolean;
|
||||
on: boolean;
|
||||
drive: DriveNumber;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
function getDriveState(drive: Drive): DriveState {
|
||||
const result: DriveState = {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
tracks: [] as string[],
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
if (!isNibbleDrive(drive)) {
|
||||
throw Error('No tracks.');
|
||||
}
|
||||
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
||||
result.tracks.push(base64_encode(drive.tracks[idx]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
function setDriveState(state: DriveState) {
|
||||
const result: Drive = {
|
||||
format: state.format,
|
||||
volume: state.volume,
|
||||
tracks: [] as memory[],
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
readOnly: state.readOnly,
|
||||
dirty: state.dirty
|
||||
};
|
||||
for (let idx = 0; idx < state.tracks.length; idx++) {
|
||||
result.tracks!.push(base64_decode(state.tracks[idx]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates the 16-sector and 13-sector versions of the Disk ][ drive and controller.
|
||||
*/
|
||||
export default class DiskII {
|
||||
|
||||
private drives: Drive[] = [
|
||||
{ // Drive 1
|
||||
format: 'dsk',
|
||||
volume: 254,
|
||||
tracks: [],
|
||||
track: 0,
|
||||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
},
|
||||
{ // Drive 2
|
||||
format: 'dsk',
|
||||
volume: 254,
|
||||
tracks: [],
|
||||
track: 0,
|
||||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
}];
|
||||
|
||||
private skip = 0;
|
||||
/** Last data written by the CPU to card softswitch 0x8D. */
|
||||
private bus = 0;
|
||||
/** Drive data register. */
|
||||
private latch = 0;
|
||||
/** Drive off timeout id or null. */
|
||||
private offTimeout: number | null = null;
|
||||
/** Q6 (Shift/Load): Used by WOZ disks. */
|
||||
private q6 = 0;
|
||||
/** Q7 (Read/Write): Used by WOZ disks. */
|
||||
private q7: boolean = false;
|
||||
/** Q7 (Read/Write): Used by Nibble disks. */
|
||||
private writeMode = false;
|
||||
/** Whether the selected drive is on. */
|
||||
private on = false;
|
||||
/** Current drive number (0, 1). */
|
||||
private drive: DriveNumber = 1;
|
||||
/** Current drive object. */
|
||||
private cur = this.drives[this.drive - 1];
|
||||
|
||||
/** Q0-Q3: Coil states. */
|
||||
private q = [false, false, false, false];
|
||||
|
||||
/** The 8-cycle LSS clock. */
|
||||
private clock: LssClockCycle = 0;
|
||||
/** Current CPU cycle count. */
|
||||
private lastCycles = 0;
|
||||
/** Current state of the Logic State Sequencer. */
|
||||
private state: nibble = 0;
|
||||
/**
|
||||
* Number of zeros read in a row. The Disk ][ can only read two zeros in a
|
||||
* row reliably; above that and the drive starts reporting garbage. See
|
||||
* "Freaking Out Like a MC3470" in the WOZ spec.
|
||||
*/
|
||||
private zeros = 0;
|
||||
|
||||
/** Contents of the P5 ROM at 0xCnXX. */
|
||||
private bootstrapRom: rom;
|
||||
/** Contents of the P6 ROM. */
|
||||
private sequencerRom: typeof SEQUENCER_ROM_16 | typeof SEQUENCER_ROM_13;
|
||||
|
||||
/** Builds a new Disk ][ card. */
|
||||
constructor(private io: Apple2IO, private callbacks: Callbacks, private sectors = 16) {
|
||||
this.lastCycles = this.io.cycles();
|
||||
this.bootstrapRom = this.sectors == 16 ? BOOTSTRAP_ROM_16 : BOOTSTRAP_ROM_13;
|
||||
this.sequencerRom = this.sectors == 16 ? SEQUENCER_ROM_16 : SEQUENCER_ROM_13;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private debug(..._args: any) {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.debug('Disk ][');
|
||||
}
|
||||
|
||||
// Only used for WOZ disks
|
||||
private moveHead() {
|
||||
if (isNibbleDrive(this.cur)) {
|
||||
return;
|
||||
}
|
||||
const track: bit[] =
|
||||
this.cur.rawTracks[this.cur.trackMap[this.cur.track]] || [0];
|
||||
|
||||
const cycles = this.io.cycles();
|
||||
|
||||
// Spin the disk the number of elapsed cycles since last call
|
||||
let workCycles = (cycles - this.lastCycles) * 2;
|
||||
this.lastCycles = cycles;
|
||||
|
||||
while (workCycles-- > 0) {
|
||||
let pulse: bit = 0;
|
||||
if (this.clock == 4) {
|
||||
pulse = track[this.cur.head];
|
||||
if (!pulse) {
|
||||
// More that 2 zeros can not be read reliably.
|
||||
if (++this.zeros > 2) {
|
||||
pulse = Math.random() >= 0.5 ? 1 : 0;
|
||||
}
|
||||
} else {
|
||||
this.zeros = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let idx = 0;
|
||||
idx |= pulse ? 0x00 : 0x01;
|
||||
idx |= this.latch & 0x80 ? 0x02 : 0x00;
|
||||
idx |= this.q6 ? 0x04 : 0x00;
|
||||
idx |= this.q7 ? 0x08 : 0x00;
|
||||
idx |= this.state << 4;
|
||||
|
||||
const command = this.sequencerRom[idx];
|
||||
|
||||
if (this.on && this.q7) {
|
||||
debug('clock:', this.clock, 'command:', toHex(command), 'q6:', this.q6);
|
||||
}
|
||||
|
||||
switch (command & 0xf) {
|
||||
case 0x0: // CLR
|
||||
this.latch = 0;
|
||||
break;
|
||||
case 0x8: // NOP
|
||||
break;
|
||||
case 0x9: // SL0
|
||||
this.latch = (this.latch << 1) & 0xff;
|
||||
break;
|
||||
case 0xA: // SR
|
||||
this.latch >>= 1;
|
||||
if (this.cur.readOnly) {
|
||||
this.latch |= 0x80;
|
||||
}
|
||||
break;
|
||||
case 0xB: // LD
|
||||
this.latch = this.bus;
|
||||
debug('Loading', toHex(this.latch), 'from bus');
|
||||
break;
|
||||
case 0xD: // SL1
|
||||
this.latch = ((this.latch << 1) | 0x01) & 0xff;
|
||||
break;
|
||||
}
|
||||
this.state = (command >> 4 & 0xF) as nibble;
|
||||
|
||||
if (this.clock == 4) {
|
||||
if (this.on) {
|
||||
if (this.q7) {
|
||||
track[this.cur.head] = this.state & 0x8 ? 0x01 : 0x00;
|
||||
debug('Wrote', this.state & 0x8 ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
if (++this.cur.head >= track.length) {
|
||||
this.cur.head = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++this.clock > 7) {
|
||||
this.clock = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only called for non-WOZ disks
|
||||
private readWriteNext() {
|
||||
if (!isNibbleDrive(this.cur)) {
|
||||
return;
|
||||
}
|
||||
if (this.skip || this.writeMode) {
|
||||
const track = this.cur.tracks![this.cur.track >> 2];
|
||||
if (track && track.length) {
|
||||
if (this.cur.head >= track.length) {
|
||||
this.cur.head = 0;
|
||||
}
|
||||
|
||||
if (this.writeMode) {
|
||||
if (!this.cur.readOnly) {
|
||||
track[this.cur.head] = this.bus;
|
||||
if (!this.cur.dirty) {
|
||||
this.updateDirty(this.drive, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.latch = track[this.cur.head];
|
||||
}
|
||||
|
||||
++this.cur.head;
|
||||
}
|
||||
} else {
|
||||
this.latch = 0;
|
||||
}
|
||||
this.skip = (++this.skip % 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the head positioning stepper motor coil for the given
|
||||
* phase is on or off. Normally, the motor must be stepped two phases
|
||||
* per track. Half tracks can be written by stepping only once; quarter
|
||||
* tracks by activating two neighboring coils at once.
|
||||
*/
|
||||
private setPhase(phase: Phase, on: boolean) {
|
||||
this.debug('phase ' + phase + (on ? ' on' : ' off'));
|
||||
if (on) {
|
||||
this.cur.track += PHASE_DELTA[this.cur.phase][phase] * 2;
|
||||
this.cur.phase = phase;
|
||||
}
|
||||
|
||||
const maxTrack = isNibbleDrive(this.cur)
|
||||
? this.cur.tracks.length * 4 - 1
|
||||
: this.cur.trackMap.length - 1;
|
||||
if (this.cur.track > maxTrack) {
|
||||
this.cur.track = maxTrack;
|
||||
}
|
||||
if (this.cur.track < 0x0) {
|
||||
this.cur.track = 0x0;
|
||||
}
|
||||
|
||||
// debug(
|
||||
// 'Drive', _drive, 'track', toHex(_cur.track >> 2) + '.' + (_cur.track & 0x3),
|
||||
// '(' + toHex(_cur.track) + ')',
|
||||
// '[' + phase + ':' + (on ? 'on' : 'off') + ']');
|
||||
|
||||
this.q[phase] = on;
|
||||
}
|
||||
|
||||
private access(off: byte, val?: byte) {
|
||||
let result = 0;
|
||||
const readMode = val === undefined;
|
||||
|
||||
switch (off & 0x8f) {
|
||||
case LOC.PHASE0OFF: // 0x00
|
||||
this.setPhase(0, false);
|
||||
break;
|
||||
case LOC.PHASE0ON: // 0x01
|
||||
this.setPhase(0, true);
|
||||
break;
|
||||
case LOC.PHASE1OFF: // 0x02
|
||||
this.setPhase(1, false);
|
||||
break;
|
||||
case LOC.PHASE1ON: // 0x03
|
||||
this.setPhase(1, true);
|
||||
break;
|
||||
case LOC.PHASE2OFF: // 0x04
|
||||
this.setPhase(2, false);
|
||||
break;
|
||||
case LOC.PHASE2ON: // 0x05
|
||||
this.setPhase(2, true);
|
||||
break;
|
||||
case LOC.PHASE3OFF: // 0x06
|
||||
this.setPhase(3, false);
|
||||
break;
|
||||
case LOC.PHASE3ON: // 0x07
|
||||
this.setPhase(3, true);
|
||||
break;
|
||||
|
||||
case LOC.DRIVEOFF: // 0x08
|
||||
if (!this.offTimeout) {
|
||||
if (this.on) {
|
||||
// TODO(flan): This is fragile because it relies on
|
||||
// wall-clock time instead of emulator time.
|
||||
this.offTimeout = window.setTimeout(() => {
|
||||
this.debug('Drive Off');
|
||||
this.on = false;
|
||||
if (this.callbacks.driveLight) {
|
||||
this.callbacks.driveLight(this.drive, false);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LOC.DRIVEON: // 0x09
|
||||
if (this.offTimeout) {
|
||||
// TODO(flan): Fragile—see above
|
||||
window.clearTimeout(this.offTimeout);
|
||||
this.offTimeout = null;
|
||||
}
|
||||
if (!this.on) {
|
||||
this.debug('Drive On');
|
||||
this.on = true;
|
||||
this.lastCycles = this.io.cycles();
|
||||
if (this.callbacks.driveLight) {
|
||||
this.callbacks.driveLight(this.drive, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVE1: // 0x0a
|
||||
this.debug('Disk 1');
|
||||
this.drive = 1;
|
||||
this.cur = this.drives[this.drive - 1];
|
||||
if (this.on && this.callbacks.driveLight) {
|
||||
this.callbacks.driveLight(2, false);
|
||||
this.callbacks.driveLight(1, true);
|
||||
}
|
||||
break;
|
||||
case LOC.DRIVE2: // 0x0b
|
||||
this.debug('Disk 2');
|
||||
this.drive = 2;
|
||||
this.cur = this.drives[this.drive - 1];
|
||||
if (this.on && this.callbacks.driveLight) {
|
||||
this.callbacks.driveLight(1, false);
|
||||
this.callbacks.driveLight(2, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEREAD: // 0x0c (Q6L) Shift
|
||||
this.q6 = 0;
|
||||
if (this.writeMode) {
|
||||
this.debug('clearing _q6/SHIFT');
|
||||
}
|
||||
if (isNibbleDrive(this.cur)) {
|
||||
this.readWriteNext();
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEWRITE: // 0x0d (Q6H) LOAD
|
||||
this.q6 = 1;
|
||||
if (this.writeMode) {
|
||||
this.debug('setting _q6/LOAD');
|
||||
}
|
||||
if (isNibbleDrive(this.cur)) {
|
||||
if (readMode && !this.writeMode) {
|
||||
if (this.cur.readOnly) {
|
||||
this.latch = 0xff;
|
||||
this.debug('Setting readOnly');
|
||||
} else {
|
||||
this.latch = this.latch >> 1;
|
||||
this.debug('Clearing readOnly');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LOC.DRIVEREADMODE: // 0x0e (Q7L)
|
||||
this.debug('Read Mode');
|
||||
this.q7 = false;
|
||||
this.writeMode = false;
|
||||
break;
|
||||
case LOC.DRIVEWRITEMODE: // 0x0f (Q7H)
|
||||
this.debug('Write Mode');
|
||||
this.q7 = false;
|
||||
this.writeMode = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.moveHead();
|
||||
|
||||
if (readMode) {
|
||||
// According to UtAIIe, p. 9-13 to 9-14, any even address can be
|
||||
// used to read the data register onto the CPU bus, although some
|
||||
// also cause conflicts with the disk controller commands.
|
||||
if ((off & 0x01) === 0) {
|
||||
result = this.latch;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
// It's not explicitly stated, but writes to any address set the
|
||||
// data register.
|
||||
this.bus = val!;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private updateDirty(drive: DriveNumber, dirty: boolean) {
|
||||
this.drives[drive - 1].dirty = dirty;
|
||||
if (this.callbacks.dirty) {
|
||||
this.callbacks.dirty(drive, dirty);
|
||||
}
|
||||
}
|
||||
|
||||
ioSwitch(off: byte, val?: byte) {
|
||||
return this.access(off, val);
|
||||
}
|
||||
|
||||
read(_page: byte, off: byte) {
|
||||
return this.bootstrapRom[off];
|
||||
}
|
||||
|
||||
write() { }
|
||||
|
||||
reset() {
|
||||
if (this.on) {
|
||||
this.callbacks.driveLight(this.drive, false);
|
||||
this.writeMode = false;
|
||||
this.on = false;
|
||||
this.drive = 1;
|
||||
this.cur = this.drives[this.drive - 1];
|
||||
}
|
||||
for (let idx = 0; idx < 4; idx++) {
|
||||
this.q[idx] = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.moveHead();
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
getState() {
|
||||
const result = {
|
||||
drives: [] as DriveState[],
|
||||
skip: this.skip,
|
||||
latch: this.latch,
|
||||
writeMode: this.writeMode,
|
||||
on: this.on,
|
||||
drive: this.drive
|
||||
};
|
||||
this.drives.forEach(function (drive, idx) {
|
||||
result.drives[idx] = getDriveState(drive);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
setState(state: State) {
|
||||
this.skip = state.skip;
|
||||
this.latch = state.latch;
|
||||
this.writeMode = state.writeMode;
|
||||
this.on = state.on;
|
||||
this.drive = state.drive;
|
||||
for (const d of DRIVE_NUMBERS) {
|
||||
this.drives[d - 1] = setDriveState(state.drives[d - 1]);
|
||||
this.callbacks.driveLight(d, this.on);
|
||||
this.callbacks.dirty(d, this.drives[d - 1].dirty);
|
||||
}
|
||||
this.cur = this.drives[this.drive - 1];
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
getMetadata(driveNo: DriveNumber) {
|
||||
const drive = this.drives[driveNo - 1];
|
||||
if (isNibbleDrive(drive)) {
|
||||
return {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work on WOZ disks
|
||||
rwts(disk: DriveNumber, track: byte, sector: byte) {
|
||||
const cur = this.drives[disk - 1];
|
||||
if (!isNibbleDrive(cur)) {
|
||||
throw new Error('Can\'t read WOZ disks');
|
||||
}
|
||||
return readSector(cur, track, sector);
|
||||
}
|
||||
|
||||
/** Sets the data for `drive` from `disk`, which is expected to be JSON. */
|
||||
// TODO(flan): This implementation is not very safe.
|
||||
setDisk(drive: DriveNumber, disk: any) {
|
||||
const fmt = disk.type as DiskFormat;
|
||||
const readOnly = disk.readOnly;
|
||||
const name = disk.name;
|
||||
|
||||
let data: memory[] | memory[][];
|
||||
if (disk.encoding == 'base64') {
|
||||
data = [];
|
||||
for (let t = 0; t < disk.data.length; t++) {
|
||||
if (fmt == 'nib') {
|
||||
data[t] = base64_decode(disk.data[t] as string);
|
||||
} else {
|
||||
data[t] = [];
|
||||
for (let s = 0; s < disk.data[t].length; s++) {
|
||||
data[t][s] = base64_decode(disk.data[t][s] as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = disk.data;
|
||||
}
|
||||
const cur = this.drives[drive - 1];
|
||||
|
||||
// var v = (fmt === 'dsk' ? data[0x11][0x00][0x06] : 0xfe);
|
||||
// if (v == 0x00) {
|
||||
const volume = disk.volume || 0xfe;
|
||||
// }
|
||||
|
||||
const options = {
|
||||
volume,
|
||||
readOnly,
|
||||
name,
|
||||
data
|
||||
};
|
||||
|
||||
let newDisk: Disk;
|
||||
switch (fmt) {
|
||||
case 'd13':
|
||||
newDisk = D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
newDisk = DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
newDisk = Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
newDisk = ProDOS(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(cur, newDisk);
|
||||
this.updateDirty(this.drive, false);
|
||||
}
|
||||
|
||||
getJSON(drive: DriveNumber, pretty: boolean) {
|
||||
const cur = this.drives[drive - 1];
|
||||
if (!isNibbleDrive(cur)) {
|
||||
throw new Error('Can\'t save WOZ disks to JSON');
|
||||
}
|
||||
return jsonEncode(cur, pretty);
|
||||
}
|
||||
|
||||
setJSON(drive: DriveNumber, data: any) {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, jsonDecode(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
setBinary(drive: DriveNumber, name: string, fmt: DiskFormat, rawData: memory) {
|
||||
let disk;
|
||||
const cur = this.drives[drive - 1];
|
||||
const readOnly = false;
|
||||
const volume = 254;
|
||||
const options = {
|
||||
name,
|
||||
rawData,
|
||||
readOnly,
|
||||
volume
|
||||
};
|
||||
|
||||
switch (fmt) {
|
||||
case '2mg':
|
||||
disk = _2MG(options);
|
||||
break;
|
||||
case 'd13':
|
||||
disk = D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
disk = DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
disk = Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
disk = ProDOS(options);
|
||||
break;
|
||||
case 'woz':
|
||||
disk = Woz(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(cur, disk);
|
||||
this.updateDirty(drive, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work with WOZ disks
|
||||
getBinary(drive: DriveNumber) {
|
||||
const cur = this.drives[drive - 1];
|
||||
if (!isNibbleDrive(cur)) {
|
||||
return null;
|
||||
}
|
||||
// TODO(flan): Assumes 16-sectors
|
||||
const len = (16 * cur.tracks.length * 256);
|
||||
const data = new Uint8Array(len);
|
||||
|
||||
let idx = 0;
|
||||
for (let t = 0; t < cur.tracks.length; t++) {
|
||||
if (cur.format === 'nib') {
|
||||
data.set(cur.tracks[t], idx);
|
||||
idx += cur.tracks[t].length;
|
||||
} else {
|
||||
for (let s = 0; s < 0x10; s++) {
|
||||
const sector = readSector(cur, t, s);
|
||||
data.set(sector, idx);
|
||||
idx += sector.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work with WOZ disks
|
||||
getBase64(drive: DriveNumber) {
|
||||
const cur = this.drives[drive - 1];
|
||||
if (!isNibbleDrive(cur)) {
|
||||
return null;
|
||||
}
|
||||
const data: string[][] | string[] = [];
|
||||
for (let t = 0; t < cur.tracks.length; t++) {
|
||||
if (cur.format === 'nib') {
|
||||
data[t] = base64_encode(cur.tracks[t]);
|
||||
} else {
|
||||
const track: string[] = [];
|
||||
for (let s = 0; s < 0x10; s++) {
|
||||
track[s] = base64_encode(readSector(cur, t, s));
|
||||
}
|
||||
data[t] = track;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
@ -9,12 +9,12 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { byte, memory } from '../types';
|
||||
import { byte, DiskFormat, memory } from '../types';
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { bytify, debug, toHex } from '../util';
|
||||
|
||||
export type Disk = {
|
||||
format: string,
|
||||
format: DiskFormat,
|
||||
name: string,
|
||||
volume: byte,
|
||||
tracks: memory[],
|
||||
@ -22,11 +22,11 @@ export type Disk = {
|
||||
};
|
||||
|
||||
export type Drive = {
|
||||
format: string,
|
||||
volume: 254,
|
||||
format: DiskFormat,
|
||||
volume: byte,
|
||||
tracks: memory[],
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,6 +315,7 @@ export function explodeSector13(volume: byte, track: byte, sector: byte, data: b
|
||||
return buf;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work on WOZ disks
|
||||
export function readSector(drive: Drive, track: byte, sector: byte) {
|
||||
const _sector = drive.format == 'po' ? _PO[sector] : _DO[sector];
|
||||
let val, state = 0;
|
||||
@ -338,7 +339,7 @@ export function readSector(drive: Drive, track: byte, sector: byte) {
|
||||
}
|
||||
}
|
||||
let t = 0, s = 0, v = 0, checkSum;
|
||||
const data = [];
|
||||
const data = new Uint8Array(256);
|
||||
while (retry < 4) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
@ -407,7 +408,7 @@ export function readSector(drive: Drive, track: byte, sector: byte) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
return new Uint8Array();
|
||||
}
|
||||
|
||||
export function jsonEncode(cur: Drive, pretty: boolean) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const apple2_charset = bytify([
|
||||
const apple2_charset = new Uint8Array([
|
||||
0x00,0x1c,0x22,0x2a,0x2e,0x2c,0x20,0x1e,
|
||||
0x00,0x08,0x14,0x22,0x22,0x3e,0x22,0x22,
|
||||
0x00,0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c,
|
||||
@ -257,6 +257,6 @@ const apple2_charset = bytify([
|
||||
0x80,0x80,0x80,0xbe,0x80,0xbe,0x80,0x80,
|
||||
0x80,0x90,0x88,0x84,0x82,0x84,0x88,0x90,
|
||||
0x80,0x9c,0xa2,0x84,0x88,0x88,0x80,0x88
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default apple2_charset;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
@ -2052,7 +2052,7 @@ const rom = bytify([
|
||||
0x8c,0xc4,0x96,0xaf,0x17,0x17,0x2b,0x1f,
|
||||
0x83,0x7f,0x5d,0xcc,0xb5,0xfc,0x17,0x17,
|
||||
0xf5,0x03,0xfb,0x03,0x62,0xfa,0x40,0xfa
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class Apple2eROM extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
/* exported apple2e_charset */
|
||||
|
||||
const apple2e_charset = bytify([
|
||||
const apple2e_charset = new Uint8Array([
|
||||
0x1c,0x22,0x2a,0x3a,0x1a,0x02,0x3c,0x00,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x1e,0x22,0x22,0x1e,0x22,0x22,0x1e,0x00,
|
||||
@ -515,6 +515,6 @@ const apple2e_charset = bytify([
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default apple2e_charset;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@ -2050,7 +2050,7 @@ const rom = bytify([
|
||||
0x8c, 0xd6, 0x96, 0xaf, 0x17, 0x17, 0x2b, 0x1f,
|
||||
0x83, 0x7f, 0x5d, 0xcc, 0xb5, 0xfc, 0x17, 0x17,
|
||||
0xf5, 0x03, 0xfb, 0x03, 0x62, 0xfa, 0xfa, 0xc3
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class Apple2eEnhancedROM extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const apple2enh_charset = bytify([
|
||||
const apple2enh_charset = new Uint8Array([
|
||||
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 +513,6 @@ const apple2enh_charset = bytify([
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default apple2enh_charset;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0x6f,0xd8,0x65,0xd7,0xf8,0xdc,0x94,0xd9,
|
||||
0xb1,0xdb,0x30,0xf3,0xd8,0xdf,0xe1,0xdb,
|
||||
0x8f,0xf3,0x98,0xf3,0xe4,0xf1,0xdd,0xf1,
|
||||
@ -1538,7 +1538,7 @@ const rom = bytify([
|
||||
0x8c,0xc4,0x96,0xaf,0x17,0x17,0x2b,0x1f,
|
||||
0x83,0x7f,0x5d,0xcc,0xb5,0xfc,0x17,0x17,
|
||||
0xf5,0x03,0xfb,0x03,0x62,0xfa,0x40,0xfa,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class Apple2jROM extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const apple2j_charset = bytify([
|
||||
const apple2j_charset = new Uint8Array([
|
||||
0xff,0xef,0xe1,0xed,0xd5,0xfb,0xf7,0xef,
|
||||
0xff,0xfb,0xc7,0xf7,0xc1,0xf7,0xf7,0xef,
|
||||
0xff,0xff,0xd5,0xd5,0xfd,0xfd,0xfb,0xf7,
|
||||
@ -257,6 +257,6 @@ const apple2j_charset = bytify([
|
||||
0x00,0x00,0x00,0x3e,0x00,0x3e,0x00,0x00,
|
||||
0x00,0x10,0x08,0x04,0x02,0x04,0x08,0x10,
|
||||
0x00,0x1c,0x22,0x04,0x08,0x08,0x00,0x08,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default apple2j_charset;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const apple2lc_charset = bytify([
|
||||
const apple2lc_charset = new Uint8Array([
|
||||
0x1c,0x22,0x2a,0x2a,0x2c,0x20,0x1e,0x00,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x3c,0x22,0x22,0x3c,0x22,0x22,0x3c,0x00,
|
||||
@ -257,6 +257,6 @@ const apple2lc_charset = bytify([
|
||||
0x30,0x08,0x08,0x06,0x08,0x08,0x30,0x00,
|
||||
0x00,0x00,0x10,0x2a,0x04,0x00,0x00,0x00,
|
||||
0x2a,0x14,0x2a,0x14,0x2a,0x14,0x2a,0x00
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default apple2lc_charset;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
/*
|
||||
* http://dreher.net/?s=projects/CFforAppleII&c=projects/CFforAppleII/downloads1.php
|
||||
*/
|
||||
|
||||
export const rom = bytify([
|
||||
export const rom = new Uint8Array([
|
||||
0x43, 0x46, 0x46, 0x41, 0x20, 0x46, 0x69, 0x72,
|
||||
0x6d, 0x77, 0x61, 0x72, 0x65, 0x0d, 0x0d, 0x0d,
|
||||
0x53, 0x65, 0x65, 0x20, 0x3c, 0x68, 0x74, 0x74,
|
||||
@ -517,4 +517,4 @@ export const rom = bytify([
|
||||
0x55, 0x49, 0xd4, 0x77, 0x77, 0x77, 0x77, 0xff,
|
||||
0x9d, 0x00, 0xc8, 0xa0, 0x00, 0x88, 0xf0, 0x05,
|
||||
0xdd, 0x00, 0xc8, 0xd0, 0xf8, 0x98, 0x60, 0xff
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
export const P5_16 = bytify([
|
||||
export const BOOTSTRAP_ROM_16 = new Uint8Array([
|
||||
0xa2,0x20,0xa0,0x00,0xa2,0x03,0x86,0x3c,
|
||||
0x8a,0x0a,0x24,0x3c,0xf0,0x10,0x05,0x3c,
|
||||
0x49,0xff,0x29,0x7e,0xb0,0x08,0x4a,0xd0,
|
||||
@ -33,9 +33,9 @@ export const P5_16 = bytify([
|
||||
0xc8,0xd0,0xee,0xe6,0x27,0xe6,0x3d,0xa5,
|
||||
0x3d,0xcd,0x00,0x08,0xa6,0x2b,0x90,0xdb,
|
||||
0x4c,0x01,0x08,0x00,0x00,0x00,0x00,0x00
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export const P5_13 = bytify([
|
||||
export const BOOTSTRAP_ROM_13 = new Uint8Array([
|
||||
0xa2,0x20,0xa0,0x00,0xa9,0x03,0x85,0x3c,
|
||||
0x18,0x88,0x98,0x24,0x3c,0xf0,0xf5,0x26,
|
||||
0x3c,0x90,0xf8,0xc0,0xd5,0xf0,0xed,0xca,
|
||||
@ -68,4 +68,4 @@ export const P5_13 = bytify([
|
||||
0x91,0x26,0xc8,0xe8,0xe0,0x33,0xd0,0xe4,
|
||||
0xc6,0x2a,0xd0,0xde,0xcc,0x00,0x03,0xd0,
|
||||
0x03,0x4c,0x01,0x03,0x4c,0x2d,0xff,0xff
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
export const rom = bytify([
|
||||
export const rom = new Uint8Array([
|
||||
0x18,0xb0,0x38,0x48,0x8a,0x48,0x98,0x48,
|
||||
0x08,0x78,0x20,0x58,0xff,0xba,0x68,0x68,
|
||||
0x68,0x68,0xa8,0xca,0x9a,0x68,0x28,0xaa,
|
||||
@ -33,4 +33,4 @@ export const rom = bytify([
|
||||
0xa8,0xbd,0x38,0x07,0xc5,0x24,0x68,0xb0,
|
||||
0x05,0x48,0x29,0x80,0x09,0x20,0x2c,0x58,
|
||||
0xff,0xf0,0x03,0xfe,0x38,0x07,0x70,0x84
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
export const rom = bytify([
|
||||
export const rom = new Uint8Array([
|
||||
0x43,0x4f,0x50,0x59,0x52,0x49,0x47,0x48,
|
||||
0x54,0x20,0x28,0x43,0x29,0x20,0x31,0x39,
|
||||
0x38,0x36,0x2d,0x38,0x39,0x20,0x41,0x50,
|
||||
@ -1025,4 +1025,4 @@ export const rom = bytify([
|
||||
0x05,0x09,0xaa,0x68,0xce,0x02,0x09,0xd0,
|
||||
0xf1,0xc8,0xd0,0xd7,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
/*
|
||||
$Cn01=$20
|
||||
@ -7,7 +7,7 @@ import { bytify } from '../../util';
|
||||
$Cn07=$00 Smartport / $3C Disk controller
|
||||
*/
|
||||
|
||||
export const rom = bytify([
|
||||
export const rom = new Uint8Array([
|
||||
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,
|
||||
@ -24,4 +24,4 @@ export const rom = bytify([
|
||||
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,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
export const rom = bytify([
|
||||
export const rom = new Uint8Array([
|
||||
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
|
||||
@ -257,4 +257,4 @@ export const rom = bytify([
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../../util';
|
||||
import { ReadonlyUint8Array } from '../../types';
|
||||
|
||||
export const ROM = bytify([
|
||||
export const ROM = new Uint8Array([
|
||||
0xad,0x7b,0x07,0x29,0xf8,0xc9,0x30,0xf0,
|
||||
0x21,0xa9,0x30,0x8d,0x7b,0x07,0x8d,0xfb,
|
||||
0x07,0xa9,0x00,0x8d,0xfb,0x06,0x20,0x61,
|
||||
@ -129,9 +129,9 @@ export const ROM = bytify([
|
||||
0x05,0xe9,0x47,0x90,0xd4,0x69,0x1f,0x18,
|
||||
0x90,0xd1,0x60,0x38,0x71,0xb2,0x7b,0x00,
|
||||
0x48,0x66,0xc4,0xc2,0xc1,0xff,0xc3,0xea,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export const VIDEO_ROM = bytify([
|
||||
export const VIDEO_ROM = new Uint8Array([
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,
|
||||
@ -388,4 +388,4 @@ export const VIDEO_ROM = bytify([
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x24,0x48,0x92,0x24,0x48,0x92,0x24,0x48,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0x6f, 0xd8, 0x65, 0xd7, 0xf8, 0xdc, 0x94, 0xd9,
|
||||
0xb1, 0xdb, 0x30, 0xf3, 0xd8, 0xdf, 0xe1, 0xdb,
|
||||
0x8f, 0xf3, 0x98, 0xf3, 0xe4, 0xf1, 0xdd, 0xf1,
|
||||
@ -1538,7 +1538,7 @@ const rom = bytify([
|
||||
0x8c, 0xc4, 0x96, 0xaf, 0x17, 0x17, 0x2b, 0x1f,
|
||||
0x83, 0x7f, 0x5d, 0xcc, 0xb5, 0xfc, 0x17, 0x17,
|
||||
0xf5, 0x03, 0xfb, 0x03, 0x62, 0xfa, 0x59, 0xff
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class Apple2ROM extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0xa9, 0x20, 0x8d, 0x26, 0x03, 0xad, 0x57, 0xc0,
|
||||
0xad, 0x53, 0xc0, 0xad, 0x50, 0xc0, 0xa9, 0x00,
|
||||
0x85, 0x1c, 0xad, 0x26, 0x03, 0x85, 0x1b, 0xa0,
|
||||
@ -1538,7 +1538,7 @@ const rom = bytify([
|
||||
0x8c, 0xc4, 0x96, 0xaf, 0x17, 0x17, 0x2b, 0x1f,
|
||||
0x83, 0x7f, 0x5d, 0xcc, 0xb5, 0xfc, 0x17, 0x17,
|
||||
0xf5, 0x03, 0xfb, 0x03, 0x62, 0xfa, 0x40, 0xfa
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class IntBASIC extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
import ROM from './rom';
|
||||
|
||||
const rom = bytify([
|
||||
const rom = new Uint8Array([
|
||||
0xa9, 0x20, 0x8d, 0x26, 0x03, 0xad, 0x57, 0xc0,
|
||||
0xad, 0x53, 0xc0, 0xad, 0x50, 0xc0, 0xa9, 0x00,
|
||||
0x85, 0x1c, 0xad, 0x26, 0x03, 0x85, 0x1b, 0xa0,
|
||||
@ -1538,8 +1538,7 @@ const rom = bytify([
|
||||
0x8c, 0xc3, 0x96, 0xaf, 0x17, 0x17, 0x2b, 0x1f,
|
||||
0x83, 0x7f, 0x5d, 0xcc, 0xb5, 0xfc, 0x17, 0x17,
|
||||
0xf5, 0x03, 0xfb, 0x03, 0x59, 0xff, 0x86, 0xfa,
|
||||
]);
|
||||
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default class OriginalROM extends ROM {
|
||||
constructor() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const pigfont_charset = bytify([
|
||||
const pigfont_charset = new Uint8Array([
|
||||
0x00,0x1c,0x22,0x2a,0x2e,0x20,0x1e,0x00,
|
||||
0x0c,0x12,0x21,0x3f,0x21,0x21,0x00,0x00,
|
||||
0x3e,0x11,0x1e,0x11,0x11,0x3e,0x00,0x00,
|
||||
@ -257,6 +257,6 @@ const pigfont_charset = bytify([
|
||||
0x98,0x8c,0x9e,0x97,0xff,0xff,0xef,0x9e,
|
||||
0xbe,0xc9,0xc9,0xbe,0x9c,0xc1,0xbe,0xc1,
|
||||
0x84,0x88,0xb6,0xff,0xfc,0xfc,0xbe,0xb6,
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default pigfont_charset;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { bytify } from '../util';
|
||||
import { ReadonlyUint8Array } from '../types';
|
||||
|
||||
const rmfont_charset = bytify([
|
||||
const rmfont_charset = new Uint8Array([
|
||||
0x3c,0x42,0x59,0x55,0x55,0x39,0x02,0x3c,
|
||||
0x08,0x14,0x22,0x22,0x3e,0x22,0x22,0x00,
|
||||
0x1e,0x24,0x24,0x1c,0x24,0x24,0x1e,0x00,
|
||||
@ -513,6 +513,6 @@ const rmfont_charset = bytify([
|
||||
0x82,0x82,0x22,0x88,0x82,0x82,0x00,0x00,
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
]);
|
||||
]) as ReadonlyUint8Array;
|
||||
|
||||
export default rmfont_charset;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { PageHandler } from '../cpu6502';
|
||||
import { byte, memory } from '../types';
|
||||
import { byte, rom } from '../types';
|
||||
|
||||
export default class ROM implements PageHandler {
|
||||
|
||||
constructor(
|
||||
private readonly startPage: byte,
|
||||
private readonly endPage: byte,
|
||||
private readonly rom: memory) {
|
||||
private readonly rom: rom) {
|
||||
const expectedLength = (endPage-startPage+1) * 256;
|
||||
if (rom.length != expectedLength) {
|
||||
throw Error(`rom does not have the correct length: expected ${expectedLength} was ${rom.length}`);
|
||||
|
82
js/types.ts
82
js/types.ts
@ -1,4 +1,22 @@
|
||||
|
||||
/**
|
||||
* Extracts the members of a constant array as a type. Used as:
|
||||
*
|
||||
* @example
|
||||
* const SOME_VALUES = ['a', 'b', 1, 2] as const;
|
||||
* type SomeValues = MemberOf<typeof SOME_VALUES>; // 'a' | 'b' | 1 | 2
|
||||
*/
|
||||
export type MemberOf<T extends ReadonlyArray<unknown>> =
|
||||
T extends ReadonlyArray<infer E> ? E : never;
|
||||
|
||||
/** A bit. */
|
||||
export type bit = 0 | 1;
|
||||
|
||||
/** A nibble. */
|
||||
export type nibble =
|
||||
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 |
|
||||
0x8 | 0x9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf;
|
||||
|
||||
/** A byte (0..255). This is not enforced by the compiler. */
|
||||
export type byte = number;
|
||||
|
||||
@ -6,39 +24,59 @@ export type byte = number;
|
||||
export type word = number;
|
||||
|
||||
/** A raw region of memory. */
|
||||
export type memory = number[] | Uint8Array;
|
||||
export type memory = Uint8Array;
|
||||
|
||||
/** A raw region of memory. */
|
||||
export type rom = ReadonlyUint8Array;
|
||||
|
||||
/** 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;
|
||||
/** 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 const DISK_FORMATS = [
|
||||
'2mg',
|
||||
'd13',
|
||||
'do',
|
||||
'dsk',
|
||||
'hdv',
|
||||
'po',
|
||||
'nib',
|
||||
'woz'
|
||||
] as const;
|
||||
|
||||
export type DiskFormat = MemberOf<typeof DISK_FORMATS>;
|
||||
|
||||
export interface Drive {
|
||||
format: DiskFormat,
|
||||
volume: number,
|
||||
tracks: Array<byte[] | Uint8Array>,
|
||||
trackMap: unknown,
|
||||
format: DiskFormat,
|
||||
volume: number,
|
||||
tracks: Array<byte[] | Uint8Array>,
|
||||
trackMap: unknown,
|
||||
}
|
||||
|
||||
export interface DiskIIDrive extends Drive {
|
||||
rawTracks: unknown,
|
||||
track: number,
|
||||
head: number,
|
||||
phase: number,
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
rawTracks: unknown,
|
||||
track: number,
|
||||
head: number,
|
||||
phase: number,
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
export interface Restorable<T> {
|
||||
getState(): T;
|
||||
setState(state: T): void;
|
||||
getState(): T;
|
||||
setState(state: T): void;
|
||||
}
|
||||
|
||||
// Read-only typed arrays for constants
|
||||
export type TypedArrayMutableProperties = 'copyWithin' | 'fill' | 'reverse' | 'set' | 'sort';
|
||||
export interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutableProperties> {
|
||||
readonly [n: number]: number
|
||||
}
|
@ -2,7 +2,7 @@ import MicroModal from 'micromodal';
|
||||
|
||||
import Audio from './audio';
|
||||
import DriveLights from './drive_lights';
|
||||
import { DISK_TYPES } from '../cards/disk2';
|
||||
import { DISK_FORMATS } from '../types';
|
||||
import { gamepad, configGamepad, initGamepad } from './gamepad';
|
||||
import KeyBoard from './keyboard';
|
||||
import Tape, { TAPE_TYPES } from './tape';
|
||||
@ -180,7 +180,7 @@ export function loadAjax(drive, url) {
|
||||
}).then(function(data) {
|
||||
if (data.type == 'binary') {
|
||||
loadBinary(drive, data);
|
||||
} else if (DISK_TYPES.indexOf(data.type) > -1) {
|
||||
} else if (DISK_FORMATS.indexOf(data.type) > -1) {
|
||||
loadDisk(drive, data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
@ -245,7 +245,7 @@ export function doDelete(name) {
|
||||
function doLoadLocal(drive, file) {
|
||||
var parts = file.name.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (DISK_TYPES.indexOf(ext) > -1) {
|
||||
if (DISK_FORMATS.indexOf(ext) > -1) {
|
||||
doLoadLocalDisk(drive, file);
|
||||
} else if (TAPE_TYPES.indexOf(ext) > -1) {
|
||||
tape.doLoadLocalTape(file);
|
||||
|
13
js/util.ts
13
js/util.ts
@ -30,12 +30,7 @@ export const testables = {
|
||||
* was just powered on.
|
||||
*/
|
||||
export function allocMem(size: number) {
|
||||
let result: number[] | Uint8Array;
|
||||
if (window.Uint8Array) {
|
||||
result = new Uint8Array(size);
|
||||
} else {
|
||||
result = new Array(size);
|
||||
}
|
||||
const result = new Uint8Array(size);
|
||||
|
||||
for (let idx = 0; idx < size; idx++) {
|
||||
result[idx] = (idx & 0x02) ? 0x00 : 0xff;
|
||||
@ -57,11 +52,7 @@ export function allocMemPages(pages: number): memory {
|
||||
|
||||
/** Returns a new Uint8Array for the input array. */
|
||||
export function bytify(ary: number[]): memory {
|
||||
let result: number[] | Uint8Array = ary;
|
||||
if (window.Uint8Array) {
|
||||
result = new Uint8Array(ary);
|
||||
}
|
||||
return result;
|
||||
return new Uint8Array(ary);
|
||||
}
|
||||
|
||||
/** Writes to the console. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user