diff --git a/js/base64.ts b/js/base64.ts index b0f0216..188080f 100644 --- a/js/base64.ts +++ b/js/base64.ts @@ -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); } diff --git a/js/cards/disk2.js b/js/cards/disk2.js deleted file mode 100644 index 094ae40..0000000 --- a/js/cards/disk2.js +++ /dev/null @@ -1,690 +0,0 @@ -/* Copyright 2010-2019 Will Scullin - * - * 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; - } - }; -} diff --git a/js/cards/disk2.ts b/js/cards/disk2.ts new file mode 100644 index 0000000..6ca1d9d --- /dev/null +++ b/js/cards/disk2.ts @@ -0,0 +1,884 @@ +/* Copyright 2010-2019 Will Scullin + * + * 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; + +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; + } +} \ No newline at end of file diff --git a/js/formats/format_utils.ts b/js/formats/format_utils.ts index e43c4c4..a0dc306 100644 --- a/js/formats/format_utils.ts +++ b/js/formats/format_utils.ts @@ -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) { diff --git a/js/roms/apple2_char.ts b/js/roms/apple2_char.ts index abb26fe..26e0724 100644 --- a/js/roms/apple2_char.ts +++ b/js/roms/apple2_char.ts @@ -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; diff --git a/js/roms/apple2e.ts b/js/roms/apple2e.ts index 7191f83..b2a1480 100644 --- a/js/roms/apple2e.ts +++ b/js/roms/apple2e.ts @@ -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() { diff --git a/js/roms/apple2e_char.ts b/js/roms/apple2e_char.ts index 1cb9bda..ff485d7 100644 --- a/js/roms/apple2e_char.ts +++ b/js/roms/apple2e_char.ts @@ -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; diff --git a/js/roms/apple2enh.ts b/js/roms/apple2enh.ts index 48938d3..c98b27a 100644 --- a/js/roms/apple2enh.ts +++ b/js/roms/apple2enh.ts @@ -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() { diff --git a/js/roms/apple2enh_char.ts b/js/roms/apple2enh_char.ts index 434561e..436674d 100644 --- a/js/roms/apple2enh_char.ts +++ b/js/roms/apple2enh_char.ts @@ -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; diff --git a/js/roms/apple2j.ts b/js/roms/apple2j.ts index 2b5f57f..7208428 100644 --- a/js/roms/apple2j.ts +++ b/js/roms/apple2j.ts @@ -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() { diff --git a/js/roms/apple2j_char.ts b/js/roms/apple2j_char.ts index c20bfc4..55345e5 100644 --- a/js/roms/apple2j_char.ts +++ b/js/roms/apple2j_char.ts @@ -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; diff --git a/js/roms/apple2lc_char.ts b/js/roms/apple2lc_char.ts index 803becc..932c5dc 100644 --- a/js/roms/apple2lc_char.ts +++ b/js/roms/apple2lc_char.ts @@ -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; diff --git a/js/roms/cards/cffa.ts b/js/roms/cards/cffa.ts index d62e216..cbed083 100644 --- a/js/roms/cards/cffa.ts +++ b/js/roms/cards/cffa.ts @@ -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; diff --git a/js/roms/cards/disk2.ts b/js/roms/cards/disk2.ts index 055f90c..1f623bb 100644 --- a/js/roms/cards/disk2.ts +++ b/js/roms/cards/disk2.ts @@ -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; diff --git a/js/roms/cards/parallel.ts b/js/roms/cards/parallel.ts index cc6ae49..edb0aec 100644 --- a/js/roms/cards/parallel.ts +++ b/js/roms/cards/parallel.ts @@ -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; diff --git a/js/roms/cards/ramfactor.ts b/js/roms/cards/ramfactor.ts index 0844739..6f07db7 100644 --- a/js/roms/cards/ramfactor.ts +++ b/js/roms/cards/ramfactor.ts @@ -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; diff --git a/js/roms/cards/smartport.ts b/js/roms/cards/smartport.ts index 88322ad..712d9db 100644 --- a/js/roms/cards/smartport.ts +++ b/js/roms/cards/smartport.ts @@ -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; diff --git a/js/roms/cards/thunderclock.ts b/js/roms/cards/thunderclock.ts index d081607..7752e48 100644 --- a/js/roms/cards/thunderclock.ts +++ b/js/roms/cards/thunderclock.ts @@ -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; diff --git a/js/roms/cards/videoterm.ts b/js/roms/cards/videoterm.ts index 0cc6545..c1ffa11 100644 --- a/js/roms/cards/videoterm.ts +++ b/js/roms/cards/videoterm.ts @@ -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; diff --git a/js/roms/fpbasic.ts b/js/roms/fpbasic.ts index c5ff0e9..1d9f797 100644 --- a/js/roms/fpbasic.ts +++ b/js/roms/fpbasic.ts @@ -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() { diff --git a/js/roms/intbasic.ts b/js/roms/intbasic.ts index 1f65f4c..b278778 100644 --- a/js/roms/intbasic.ts +++ b/js/roms/intbasic.ts @@ -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() { diff --git a/js/roms/original.ts b/js/roms/original.ts index 93b195f..3b90426 100644 --- a/js/roms/original.ts +++ b/js/roms/original.ts @@ -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() { diff --git a/js/roms/pigfont_char.ts b/js/roms/pigfont_char.ts index 0026166..f76f9bb 100644 --- a/js/roms/pigfont_char.ts +++ b/js/roms/pigfont_char.ts @@ -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; diff --git a/js/roms/rmfont_char.ts b/js/roms/rmfont_char.ts index ab83d1d..d566a7e 100644 --- a/js/roms/rmfont_char.ts +++ b/js/roms/rmfont_char.ts @@ -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; diff --git a/js/roms/rom.ts b/js/roms/rom.ts index 92ee960..8b86080 100644 --- a/js/roms/rom.ts +++ b/js/roms/rom.ts @@ -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}`); diff --git a/js/types.ts b/js/types.ts index bab5194..2388726 100644 --- a/js/types.ts +++ b/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; // 'a' | 'b' | 1 | 2 + */ +export type MemberOf> = + T extends ReadonlyArray ? 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; export interface Drive { - format: DiskFormat, - volume: number, - tracks: Array, - trackMap: unknown, + format: DiskFormat, + volume: number, + tracks: Array, + 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 { - 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 { + readonly [n: number]: number } \ No newline at end of file diff --git a/js/ui/apple2.js b/js/ui/apple2.js index fd58f32..5e67a82 100644 --- a/js/ui/apple2.js +++ b/js/ui/apple2.js @@ -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); diff --git a/js/util.ts b/js/util.ts index fc701d9..b3213eb 100644 --- a/js/util.ts +++ b/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. */