diff --git a/LICENSE b/LICENSE index 82e703c..7a90e4c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 whscullin +Copyright (c) 2013-2019 Will Scullin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/js/cards/disk2.js b/js/cards/disk2.js index 8f1dcc5..7b24d32 100644 --- a/js/cards/disk2.js +++ b/js/cards/disk2.js @@ -10,11 +10,27 @@ */ import { base64_decode, base64_encode } from '../base64'; -import { bytify, debug } from '../util'; +import { debug, toHex } from '../util'; +import { jsonDecode, jsonEncode, readSector } from '../formats/format_utils'; -export var DISK_TYPES = ['dsk','d13','do','po','raw','nib','2mg']; +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 default function DiskII(io, slot, callbacks) +export const DISK_TYPES = [ + '2mg', + 'd13', + 'do', + 'dsk', + 'po', + 'nib', + 'woz' +]; + +export default function DiskII(io, slot, callbacks, sectors = 16) { slot = slot || 6; var _drives = [ @@ -22,6 +38,8 @@ export default function DiskII(io, slot, callbacks) format: 'dsk', volume: 254, tracks: [], + trackMap: null, + rawTracks: null, track: 0, head: 0, phase: 0, @@ -32,6 +50,8 @@ export default function DiskII(io, slot, callbacks) format: 'dsk', volume: 254, tracks: [], + trackMap: null, + rawTracks: null, track: 0, head: 0, phase: 0, @@ -40,8 +60,12 @@ export default function DiskII(io, slot, callbacks) }]; var _skip = 0; + var _bus = 0; var _latch = 0; - var _writeMode = false; + var _offTimeout = null; + var _q6 = 0; + var _q7 = 0; + var _writeMode = false; // q7 var _on = false; var _drive = 1; var _cur = _drives[_drive - 1]; @@ -67,332 +91,138 @@ export default function DiskII(io, slot, callbacks) DRIVEWRITEMODE: 0x8F // Q7H }; - // var DO = [0x0,0x7,0xE,0x6,0xD,0x5,0xC,0x4, - // 0xB,0x3,0xA,0x2,0x9,0x1,0x8,0xF]; - var _DO = [ - 0x0,0xD,0xB,0x9,0x7,0x5,0x3,0x1, - 0xE,0xC,0xA,0x8,0x6,0x4,0x2,0xF - ]; - // var PO = [0x0,0x8,0x1,0x9,0x2,0xa,0x3,0xb, - // 0x4,0xc,0x5,0xd,0x6,0xe,0x7,0xf]; - var _PO = [ - 0x0,0x2,0x4,0x6,0x8,0xa,0xc,0xe, - 0x1,0x3,0x5,0x7,0x9,0xb,0xd,0xf - ]; + // 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 - // var D13O = [ - // 0x0, 0xa, 0x7, 0x4, 0x1, 0xb, 0x8, 0x5, 0x2, 0xc, 0x9, 0x6, 0x3 - // ]; + // Q7 Read/Write + // Q6 Shift/Load - var _D13O = [ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc - ]; - - var _trans53 = [ - 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba, - 0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb, - 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef, - 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff - ]; - - var _trans62 = [ - 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, - 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, - 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, - 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, - 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, - 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, - 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff - ]; - - var _detrans62 = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x02, 0x03, 0x00, 0x04, 0x05, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x08, - 0x00, 0x00, 0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x00, 0x00, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, - 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E, - 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x21, - 0x00, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x2A, 0x2B, - 0x00, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, - 0x00, 0x00, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x00, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F + 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() { - // console.log.apply(this, arguments); + // debug.apply(this, arguments); } function _init() { debug('Disk ][ in slot', slot); } - /** - * From Beneath Apple DOS - */ + var _clock = 0; + var _lastCycles = io.cycles(); + var _state = 0; + var _zeros = 0; - function _fourXfour(val) { - var xx = val & 0xaa; - var yy = val & 0x55; - - xx >>= 1; - xx |= 0xaa; - yy |= 0xaa; - - return [xx, yy]; - } - - function _defourXfour(xx, yy) { - return ((xx << 1) | 0x01) & yy; - } - - function _explodeSector16(volume, track, sector, data) { - var checksum; - - var buf = [], idx; - - var gap; - - /* - * Gap 1/3 (40/0x28 bytes) - */ - - if (sector === 0) // Gap 1 - gap = 0x80; - else { // Gap 3 - gap = track === 0 ? 0x28 : 0x26; + function _moveHead() { + if (!_cur.rawTracks) { + return; } + var track = _cur.rawTracks[_cur.trackMap[_cur.track]] || [0]; - for (idx = 0; idx < gap; idx++) { - buf.push(0xff); - } + var cycles = io.cycles(); + var workCycles = (cycles - _lastCycles) * 2; + _lastCycles = cycles; - /* - * Address Field - */ - - checksum = volume ^ track ^ sector; - buf = buf.concat([0xd5, 0xaa, 0x96]); // Address Prolog D5 AA 96 - buf = buf.concat(_fourXfour(volume)); - buf = buf.concat(_fourXfour(track)); - buf = buf.concat(_fourXfour(sector)); - buf = buf.concat(_fourXfour(checksum)); - buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB - - /* - * Gap 2 (5 bytes) - */ - - for (idx = 0; idx < 0x05; idx++) { - buf.push(0xff); - } - - /* - * Data Field - */ - - buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD - - var nibbles = []; - var ptr2 = 0; - var ptr6 = 0x56; - var idx2, idx6; - - for (idx = 0; idx < 0x156; idx++) { - nibbles[idx] = 0; - } - - idx2 = 0x55; - for (idx6 = 0x101; idx6 >= 0; idx6--) { - var val6 = data[idx6 % 0x100]; - var val2 = nibbles[ptr2 + idx2]; - - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - - nibbles[ptr6 + idx6] = val6; - nibbles[ptr2 + idx2] = val2; - - if (--idx2 < 0) - idx2 = 0x55; - } - - var last = 0; - for (idx = 0; idx < 0x156; idx++) { - var val = nibbles[idx]; - buf.push(_trans62[last ^ val]); - last = val; - } - buf.push(_trans62[last]); - - buf = buf.concat([0xde, 0xaa, 0xf2]); // Epilog DE AA F2 - - /* - * Gap 3 - */ - - buf.push(0xff); - - return buf; - } - - function _explodeSector13(volume, track, sector, data) { - var checksum; - - var buf = [], idx; - - var gap; - - /* - * Gap 1/3 (40/0x28 bytes) - */ - - if (sector === 0) // Gap 1 - gap = 0x80; - else { // Gap 3 - gap = track === 0 ? 0x28 : 0x26; - } - - for (idx = 0; idx < gap; idx++) { - buf.push(0xff); - } - - /* - * Address Field - */ - - checksum = volume ^ track ^ sector; - buf = buf.concat([0xd5, 0xaa, 0xb5]); // Address Prolog D5 AA B5 - buf = buf.concat(_fourXfour(volume)); - buf = buf.concat(_fourXfour(track)); - buf = buf.concat(_fourXfour(sector)); - buf = buf.concat(_fourXfour(checksum)); - buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB - - /* - * Gap 2 (5 bytes) - */ - - for (idx = 0; idx < 0x05; idx++) { - buf.push(0xff); - } - - /* - * Data Field - */ - - buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD - - var nibbles = []; - - var jdx = 0; - for (idx = 0x32; idx >= 0; idx--) { - var a5 = data[jdx] >> 3; - var a3 = data[jdx] & 0x07; - jdx++; - var b5 = data[jdx] >> 3; - var b3 = data[jdx] & 0x07; - jdx++; - var c5 = data[jdx] >> 3; - var c3 = data[jdx] & 0x07; - jdx++; - var d5 = data[jdx] >> 3; - var d3 = data[jdx] & 0x07; - jdx++; - var e5 = data[jdx] >> 3; - var e3 = data[jdx] & 0x07; - jdx++; - nibbles[idx + 0x00] = a5; - nibbles[idx + 0x33] = b5; - nibbles[idx + 0x66] = c5; - nibbles[idx + 0x99] = d5; - nibbles[idx + 0xcc] = e5; - nibbles[idx + 0x100] = a3 << 2 | (d3 & 0x4) >> 1 | (e3 & 0x4) >> 2; - nibbles[idx + 0x133] = b3 << 2 | (d3 & 0x2) | (e3 & 0x2) >> 1; - nibbles[idx + 0x166] = c3 << 2 | (d3 & 0x1) << 1 | (e3 & 0x1); - } - nibbles[0xff] = data[jdx] >> 3; - nibbles[0x199] = data[jdx] & 0x07; - - var val; - var last = 0; - for (idx = 0x199; idx >= 0x100; idx--) { - val = nibbles[idx]; - buf.push(_trans53[last ^ val]); - last = val; - } - for (idx = 0x0; idx < 0x100; idx++) { - val = nibbles[idx]; - buf.push(_trans53[last ^ val]); - last = val; - } - buf.push(_trans53[last]); - - buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB - - /* - * Gap 3 - */ - - buf.push(0xff); - - return buf; - } - - function _json_encode(drive, pretty) { - var cur = _drives[drive - 1]; - var data = []; - var format = 'dsk'; - for (var t = 0; t < cur.tracks.length; t++) { - data[t] = []; - if (cur.format === 'nib') { - format = 'nib'; - data[t] = base64_encode(cur.tracks[t]); - } else { - for (var s = 0; s < 0x10; s++) { - data[t][s] = base64_encode(_readSector(drive, t, _DO[s])); + 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; } } - } - return JSON.stringify({ - 'type': format, - 'encoding': 'base64', - 'volume': cur.volume, - 'data': data - }, null, pretty ? ' ' : null); - } - function _json_decode(drive, data) { - var _cur = _drives[drive - 1]; - var tracks = []; - var json = JSON.parse(data); - var v = json.volume; - for (var t = 0; t < json.data.length; t++) { - var track = []; - for (var s = 0; s < json.data[t].length; s++) { - var _s = 15 - s; - var d = base64_decode(json.data[t][_s]); - track = track.concat(_explodeSector16(v, t, _DO[_s], d)); + 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; } - tracks[t] = bytify(track); } - _cur.volume = v; - _cur.format = json.type; - _cur.tracks = tracks; } function _readWriteNext() { if (_skip || _writeMode) { - var track = _cur.tracks[_cur.track >> 1]; + var track = _cur.tracks[_cur.track >> 2]; if (track && track.length) { if (_cur.head >= track.length) { _cur.head = 0; @@ -417,113 +247,45 @@ export default function DiskII(io, slot, callbacks) _skip = (++_skip % 2); } - function _readSector(drive, track, sector) { - var val, state = 0; - var idx = 0; - var retry = 0; - var cur = _drives[drive - 1].tracks[track]; - - function _readNext() { - var result = cur[idx++]; - if (idx >= cur.length) { - idx = 0; - retry++; - } - return result; - } - function _skipBytes(count) { - idx += count; - if (idx >= cur.length) { - idx %= cur.length; - retry++; - } - } - var t = 0, s = 0, jdx, kdx; - var data = []; - while (retry < 4) { - switch (state) { - case 0: - val = _readNext(); - state = (val === 0xd5) ? 1 : 0; - break; - case 1: - val = _readNext(); - state = (val === 0xaa) ? 2 : 0; - break; - case 2: - val = _readNext(); - state = (val === 0x96) ? 3 : (val === 0xad ? 4 : 0); - break; - case 3: // Address - _defourXfour(_readNext(), _readNext()); // Volume - t = _defourXfour(_readNext(), _readNext()); - s = _defourXfour(_readNext(), _readNext()); - _skipBytes(5); // Skip checksum and footer - state = 0; - break; - case 4: // Data - if (s === sector && t === track) { - var data2 = []; - var last = 0; - for (jdx = 0x55; jdx >= 0; jdx--) { - val = _detrans62[_readNext() - 0x80] ^ last; - data2[jdx] = val; - last = val; - } - for (jdx = 0; jdx < 0x100; jdx++) { - val = _detrans62[_readNext() - 0x80] ^ last; - data[jdx] = val; - last = val; - } - for (kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) { - data[kdx] <<= 1; - if ((data2[jdx] & 0x01) !== 0) { - data[kdx] |= 0x01; - } - data2[jdx] >>= 1; - - data[kdx] <<= 1; - if ((data2[jdx] & 0x01) !== 0) { - data[kdx] |= 0x01; - } - data2[jdx] >>= 1; - - if (--jdx < 0) jdx = 0x55; - } - return data; - } - else - _skipBytes(0x159); // Skip data, checksum and footer - state = 0; - break; - default: - break; - } - } - return []; - } - - var _phase_delta = [[ 0, 1, 2,-1], + var _phase_delta = [ + [ 0, 1, 2,-1], [-1, 0, 1, 2], [-2,-1, 0, 1], - [ 1,-2,-1, 0]]; + [ 1,-2,-1, 0] + ]; + + var _q = [false, false, false, false]; // q0-3 function setPhase(phase, on) { - // _debug('phase ' + phase + (on ? ' on' : ' off')); - if (on) { - _cur.track += _phase_delta[_cur.phase][phase]; - _cur.phase = phase; - - if (_cur.track > _cur.tracks.length * 2 - 1) - _cur.track = _cur.tracks.length * 2 - 1; - if (_cur.track < 0x0) - _cur.track = 0x0; - - /* _debug('Drive ' + _drive + - ', track ' + toHex(_cur.track >> 1) + - ' (' + toHex(_cur.track) + ')' + - ' [' + (_cur.track % 4) + '/' + phase + ']'); */ + _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) { @@ -557,14 +319,27 @@ export default function DiskII(io, slot, callbacks) break; case LOC.DRIVEOFF: // 0x08 - _debug('Drive Off'); - _on = false; - if (callbacks.driveLight) { callbacks.driveLight(_drive, false); } + 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 - _debug('Drive On'); - _on = true; - if (callbacks.driveLight) { callbacks.driveLight(_drive, true); } + 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 @@ -586,28 +361,42 @@ export default function DiskII(io, slot, callbacks) } break; - case LOC.DRIVEREAD: // 0x0c - _readWriteNext(); + case LOC.DRIVEREAD: // 0x0c (Q6L) Shift + _q6 = 0; + if (_writeMode) { + debug('clearing _q6/SHIFT'); + } + if (!_cur.rawTracks) { + _readWriteNext(); + } break; - case LOC.DRIVEWRITE: // 0x0d - if (readMode && !_writeMode) { - if (_cur.readOnly) { - _latch = _latch | 0x80; - _debug('Setting readOnly'); - } else { - _latch = _latch & 0x7f; - _debug('Clearing readOnly'); + 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 + case LOC.DRIVEREADMODE: // 0x0e (Q7L) _debug('Read Mode'); + _q7 = 0; _writeMode = false; break; - case LOC.DRIVEWRITEMODE: // 0x0f + case LOC.DRIVEWRITEMODE: // 0x0f (Q7H) _debug('Write Mode'); + _q7 = 1; _writeMode = true; break; @@ -615,15 +404,16 @@ export default function DiskII(io, slot, callbacks) break; } + _moveHead(); + if (readMode) { if ((off & 0x01) === 0) { result = _latch; - // _debug('Read', toHex(result)); } else { result = 0; } - } else if (_writeMode) { - _latch = val; + } else { + _bus = val; } return result; @@ -634,7 +424,7 @@ export default function DiskII(io, slot, callbacks) if (callbacks.dirty) { callbacks.dirty(_drive, dirty); } } - var diskII = [ + var _P5_16 = [ 0xa2,0x20,0xa0,0x00,0xa2,0x03,0x86,0x3c, 0x8a,0x0a,0x24,0x3c,0xf0,0x10,0x05,0x3c, 0x49,0xff,0x29,0x7e,0xb0,0x08,0x4a,0xd0, @@ -667,8 +457,8 @@ export default function DiskII(io, slot, callbacks) 0xc8,0xd0,0xee,0xe6,0x27,0xe6,0x3d,0xa5, 0x3d,0xcd,0x00,0x08,0xa6,0x2b,0x90,0xdb, 0x4c,0x01,0x08,0x00,0x00,0x00,0x00,0x00 ]; - /* - var diskII_13 = [ + + var _P5_13 = [ 0xa2,0x20,0xa0,0x00,0xa9,0x03,0x85,0x3c, 0x18,0x88,0x98,0x24,0x3c,0xf0,0xf5,0x26, 0x3c,0x90,0xf8,0xc0,0xd5,0xf0,0xed,0xca, @@ -702,7 +492,9 @@ export default function DiskII(io, slot, callbacks) 0xc6,0x2a,0xd0,0xde,0xcc,0x00,0x03,0xd0, 0x03,0x4c,0x01,0x03,0x4c,0x2d,0xff,0xff ]; -*/ + + var _P5 = sectors == 16 ? _P5_16 : _P5_13; + _init(); return { @@ -711,17 +503,26 @@ export default function DiskII(io, slot, callbacks) }, read: function disk2_read(page, off) { - return diskII[off]; + return _P5[off]; }, write: function disk2_write() {}, reset: function disk2_reset() { if (_on) { + callbacks.driveLight(_drive, false); _writeMode = false; _on = false; - callbacks.driveLight(_drive, 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() { @@ -755,6 +556,7 @@ export default function DiskII(io, slot, callbacks) return result; }, + setState: function disk2_setState(state) { function setDriveState(state) { var result = { @@ -784,10 +586,7 @@ export default function DiskII(io, slot, callbacks) _drive = state.drive; _cur = _drives[_drive - 1]; }, - rwts: function disk2_rwts(disk, track, sector) { - var s = _drives[disk - 1].fmt == 'po' ? _PO[sector] : _DO[sector]; - return _readSector(disk, track, s); - }, + getMetadata: function disk_getMetadata(driveNo) { var drive = _drives[driveNo - 1]; if (drive.tracks.length) { @@ -804,10 +603,16 @@ export default function DiskII(io, slot, callbacks) 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, _s; + var data, t, s; if (disk.encoding == 'base64') { data = []; for (t = 0; t < disk.data.length; t++) { @@ -827,134 +632,86 @@ export default function DiskII(io, slot, callbacks) // var v = (fmt === 'dsk' ? data[0x11][0x00][0x06] : 0xfe); // if (v == 0x00) { - var v = disk.volume || 0xfe; + var volume = disk.volume || 0xfe; // } - cur.volume = v; - cur.readOnly = readOnly; - cur.format = fmt; - var tracks = []; + var options = { + volume, + readOnly, + name, + data + }; - for (t = 0; t < data.length; t++) { - var track = []; - if (fmt === 'nib') { - track = data[t]; - } else if (fmt === 'd13') { // DOS 3.2 Order - for (s = 0; s < data[t].length; s++) { - track = track.concat( - _explodeSector13(v, t, _D13O[s], data[t][_D13O[s]]) - ); - } - } else { - for (s = 0; s < data[t].length; s++) { - _s = 15 - s; - if (fmt === 'po') { // ProDOS Order - track = track.concat( - _explodeSector16(v, t, _PO[s], data[t][s]) - ); - } else if (fmt === 'dsk') { // DOS 3.3 Order - track = track.concat( - _explodeSector16(v, t, _DO[_s], data[t][_s]) - ); - } else { // flat - track = track.concat( - _explodeSector16(v, t, s, data[t][s]) - ); - } - } - } - tracks[t] = bytify(track); + 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; } - cur.tracks = tracks; + + Object.assign(cur, disk); _updateDirty(_drive, false); }, + getJSON: function disk2_getJSON(drive, pretty) { - return _json_encode(drive, pretty); + var cur = _drives[drive - 1]; + return jsonEncode(cur, pretty); }, + setJSON: function disk2_setJSON(drive, data) { - _json_decode(drive, data); + var cur = _drives[drive - 1]; + Object.assign(cur, jsonDecode(data)); return true; }, - setBinary: function disk2_setBinary(drive, name, fmt, data) { - var _cur = _drives[drive - 1]; - var tracks = []; - var v = 254; - if (fmt === 'do') { - fmt = 'dsk'; - } - _cur.readOnly = false; - if (fmt === '2mg') { - // Standard header size is 64 bytes. Make assumptions. - var prefix = new Uint8Array(data.slice(0, 64)); - data = data.slice(64); - // Check image format. - // Sure, it's really 64 bits. But only 2 are actually used. - switch (prefix[0xc]) { - case 0: - fmt = 'dsk'; - break; - case 1: - fmt = 'po'; - break; - case 2: - fmt = 'nib'; - break; - default: // Something hinky, assume 'dsk' - fmt = 'dsk'; - break; - } - var flags = - prefix[0x10] | (prefix[0x11] << 8) | - (prefix[0x12] << 16) | (prefix[0x13] << 24); - _cur.readOnly = (flags & 0x80000000) !== 0; - if ((flags & 0x10) !== 0) { - _cur.volume = flags & 0xff; - } else { - _cur.volume = 254; - } - } - for (var t = 0; t < 35; t++) { - var track, off, d, s, _s; - if (fmt === 'nib') { - off = t * 0x1a00; - track = new Uint8Array(data.slice(off, off + 0x1a00)); - } else if (fmt == 'd13') { // DOS 3.2 Order - track = []; - for (s = 0; s < 13; s++) { - off = (13 * t + _D13O[s]) * 256; - d = new Uint8Array(data.slice(off, off + 256)); - track = track.concat( - _explodeSector13(v, t, _D13O[s], d) - ); - } - } else { - track = []; - for (s = 0; s < 16; s++) { - _s = 15 - s; - if (fmt == 'po') { // ProDOS Order - off = (16 * t + s) * 256; - d = new Uint8Array(data.slice(off, off + 256)); - track = track.concat( - _explodeSector16(v, t, _PO[s], d) - ); - } else if (fmt == 'dsk') { // DOS 3.3 Order - off = (16 * t + _s) * 256; - d = new Uint8Array(data.slice(off, off + 256)); - track = track.concat( - _explodeSector16(v, t, _DO[_s], d) - ); - } else { - return false; - } - } - } - tracks[t] = bytify(track); - } - _cur.volume = v; - _cur.format = fmt; - _cur.tracks = tracks; + 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; }, @@ -970,7 +727,7 @@ export default function DiskII(io, slot, callbacks) data[idx++] = cur.tracks[t]; } else { for (var s = 0; s < 0x10; s++) { - var sector = _readSector(drive, t, _DO[s]); + var sector = readSector(cur, t); for (var b = 0; b < 256; b++) { data[idx++] = sector[b]; } @@ -990,7 +747,7 @@ export default function DiskII(io, slot, callbacks) data += base64_encode(cur.tracks[t]); } else { for (var s = 0; s < 0x10; s++) { - data += base64_encode(_readSector(drive, t, _DO[s])); + data += base64_encode(readSector(cur, t)); } } } diff --git a/js/cards/smartport.js b/js/cards/smartport.js new file mode 100644 index 0000000..66f13f1 --- /dev/null +++ b/js/cards/smartport.js @@ -0,0 +1,425 @@ +/* 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 } from '../base64'; +import { debug, toHex } from '../util'; + +export default function SmartPort(io, slot, cpu) { + + /* + $Cn01=$20 + $Cn03=$00 + $Cn05=$03 + $Cn07=$00 + */ + + var ROM = [ + 0xA2, 0x20, 0xA0, 0x00, 0xA2, 0x03, 0xA0, 0x3C, 0x20, 0x58, 0xFF, 0xBA, 0xBD, 0x00, 0x01, 0x0A, + 0x0A, 0x0A, 0x0A, 0xAA, 0x4C, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x60, 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, 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, + + 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, 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, 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, 0xDF, 0x20 + ]; + + var disks = []; + + function decodeDisk(unit, disk) { + disks[unit] = []; + for (var idx = 0; idx < disk.blocks.length; idx++) { + disks[unit][idx] = base64_decode(disk.blocks[idx]); + } + } + + function Address() { + var lo; + var hi; + + if (arguments.length == 1) { + lo = arguments[0] & 0xff; + hi = arguments[0] >> 8; + } else if (arguments.length == 2) { + lo = arguments[0]; + hi = arguments[1]; + } + + return { + loByte: function() { + return lo; + }, + + hiByte: function() { + return hi; + }, + + inc: function(val) { + return new Address(((hi << 8 | lo) + val) & 0xffff); + }, + + readByte: function() { + return cpu.read(hi, lo); + }, + + readWord: function() { + var readLo = this.readByte(); + var readHi = this.inc(1).readByte(); + + return readHi << 8 | readLo; + }, + + readAddress: function() { + var readLo = this.readByte(); + var readHi = this.inc(1).readByte(); + + return new Address(readLo, readHi); + }, + + writeByte: function(val) { + cpu.write(hi, lo, val); + }, + + writeWord: function(val) { + this.writeByte(val & 0xff); + this.inc(1).writeByte(val >> 8); + }, + + writeAddress: function(val) { + this.writeByte(val.loByte()); + this.inc(1).writeByte(val.hiByte()); + }, + + toString: function() { + return '$' + toHex(hi) + toHex(lo); + } + }; + } + + /* + * dumpBlock + */ + /* + function dumpBlock(drive, block) { + var result = ''; + var b; + var jdx; + + for (var idx = 0; idx < 32; idx++) { + result += toHex(idx << 4, 4) + ': '; + for (jdx = 0; jdx < 16; jdx++) { + b = disks[drive][block][idx * 16 + jdx]; + if (jdx == 8) { + result += ' '; + } + result += toHex(b) + ' '; + } + result += ' '; + for (jdx = 0; jdx < 16; jdx++) { + b = disks[drive][block][idx * 16 + jdx] & 0x7f; + if (jdx == 8) { + result += ' '; + } + if (b >= 0x20 && b < 0x7f) { + result += String.fromCharCode(b); + } else { + result += '.'; + } + } + result += '\n'; + } + return result; + } +*/ + /* + * getDeviceInfo + */ + + function getDeviceInfo(state, drive) { + if (disks[drive]) { + var blocks = disks[drive].length; + state.x = blocks & 0xff; + state.y = blocks >> 8; + + state.a = 0; + state.s &= 0xfe; + } else { + state.a = 0x28; + state.s |= 0x01; + } + } + + /* + * readBlock + */ + + function readBlock(state, drive, block, buffer) { + debug('read drive=' + drive); + debug('read buffer=' + buffer); + debug('read block=$' + toHex(block)); + + if (!disks[drive] || !disks[drive].length) { + debug('Drive', drive, 'is empty'); + return; + } + + // debug('read', '\n' + dumpBlock(drive, block)); + + for (var idx = 0; idx < 512; idx++) { + buffer.writeByte(disks[drive][block][idx]); + buffer = buffer.inc(1); + } + + state.a = 0; + state.s &= 0xfe; + } + + /* + * writeBlock + */ + + function writeBlock(state, drive, block, buffer) { + debug('write drive=' + drive); + debug('write buffer=' + buffer); + debug('write block=$' + toHex(block)); + + if (!disks[drive] || !disks[drive].length) { + debug('Drive', drive, 'is empty'); + return; + } + + // debug('write', '\n' + dumpBlock(drive, block)); + + for (var idx = 0; idx < 512; idx++) { + disks[drive][block][idx] = buffer.readByte(); + buffer = buffer.inc(1); + } + state.a = 0; + state.s &= 0xfe; + } + + /* + * formatDevice + */ + + function formatDevice(state, drive) { + for (var idx = 0; idx < disks[drive].length; idx++) { + disks[drive][idx] = []; + for (var jdx = 0; jdx < 512; jdx++) { + disks[drive][idx][jdx] = 0; + } + } + + state.a = 0; + state.s &= 0xfe; + } + + /* + * Interface + */ + + return { + read: function(page, off, debugFlag) { + var state = cpu.getState(); + var cmd; + var unit; + var buffer; + var block; + + if (!debugFlag) { + debug('read $' + toHex(page) + toHex(off) + '=$' + toHex(ROM[off]), cpu.sync()); + } + + if (off == 0x00 && cpu.sync()) { + readBlock(state, 1, 0, new Address(0x0800)); + } else if (off == 0x20 && cpu.sync()) { // Regular block device entry POINT + debug('block device entry'); + cmd = cpu.read(0x00, 0x42); + unit = cpu.read(0x00, 0x43); + var bufferAddr; + var blockAddr; + var drive = (unit & 0x80) ? 2 : 1; + var driveSlot = (unit & 0x70) >> 4; + + debug('cmd=' + cmd); + debug('unit=$' + toHex(unit)); + + debug('slot=' + driveSlot + ' drive=' + drive); + + switch (cmd) { + case 0: // INFO + getDeviceInfo(state, drive); + break; + + case 1: // READ + bufferAddr = new Address(0x44); + buffer = bufferAddr.readAddress(); + blockAddr = new Address(0x46); + block = blockAddr.readWord(); + + readBlock(state, drive, block, buffer); + break; + + case 2: // WRITE + bufferAddr = new Address(0x44); + buffer = bufferAddr.readAddress(); + blockAddr = new Address(0x46); + block = blockAddr.readWord(); + + writeBlock(state, drive, block, buffer); + break; + case 3: // FORMAT + formatDevice(state, unit); + break; + } + } else if (off == 0x23 && cpu.sync()) { + debug('smartport entry'); + var retVal = {}; + var stackAddr = new Address(state.sp + 1, 0x01); + + retVal = stackAddr.readAddress(); + + debug('return=' + retVal); + + var cmdBlockAddr = retVal.inc(1); + cmd = cmdBlockAddr.readByte(); + var cmdListAddr = cmdBlockAddr.inc(1).readAddress(); + + debug('cmd=' + cmd); + debug('cmdListAddr=' + cmdListAddr); + + stackAddr.writeAddress(retVal.inc(3)); + + var parameterCount = cmdListAddr.readByte(); + unit = cmdListAddr.inc(1).readByte(); + buffer = cmdListAddr.inc(2).readAddress(); + var status; + + debug('parameterCount=' + parameterCount); + switch (cmd) { + case 0x00: // INFO + status = cmdListAddr.inc(4).readByte(); + debug('info unit=' + unit); + debug('info buffer=' + buffer); + debug('info status=' + status); + switch (unit) { + case 0: + switch (status) { + case 0: + buffer.writeByte(1); // one device + buffer.inc(1).writeByte(1 << 6); // no interrupts + buffer.inc(2).writeByte(0); // reserved + buffer.inc(3).writeByte(0); // reserved + buffer.inc(4).writeByte(0); // reserved + buffer.inc(5).writeByte(0); // reserved + buffer.inc(6).writeByte(0); // reserved + buffer.inc(7).writeByte(0); // reserved + state.x = 8; + state.y = 0; + state.a = 0; + state.s &= 0xfe; + break; + } + break; + default: // Unit 1 + switch (status) { + case 0: + var blocks = disks[unit].length; + buffer.writeByte(0xf0); // W/R Block device in drive + buffer.inc(1).writeByte(blocks & 0xff); // 1600 blocks + buffer.inc(2).writeByte((blocks & 0xff00) >> 8); + buffer.inc(3).writeByte((blocks & 0xff0000) >> 16); + state.x = 4; + state.y = 0; + state.a = 0; + state.s &= 0xfe; + break; + } + break; + } + state.a = 0; + state.s &= 0xfe; + break; + + case 0x01: // READ BLOCK + block = cmdListAddr.inc(4).readWord(); + readBlock(state, unit, block, buffer); + break; + + case 0x02: // WRITE BLOCK + block = cmdListAddr.inc(4).readWord(); + writeBlock(state, unit, block, buffer); + break; + + case 0x03: // FORMAT + formatDevice(state, unit); + break; + + case 0x04: // CONTROL + break; + + case 0x05: // INIT + break; + + case 0x06: // OPEN + break; + + case 0x07: // CLOSE + break; + + case 0x08: // READ + break; + + case 0x09: // WRITE + break; + } + } + + cpu.setState(state); + + return ROM[off]; + }, + + write: function() { + }, + + getState: function() { + }, + + setState: function() { + }, + + setBinary: function (drive, name, fmt, data) { + disks[drive] = []; + if (fmt == '2mg') { + data = data.slice(64); + } + for (var idx = 0; idx < data.byteLength; idx += 512) { + disks[drive].push(new Uint8Array(data.slice(idx, idx + 512))); + } + }, + + setDisk: function(drive, json) { + decodeDisk(drive, json); + } + }; +} diff --git a/js/formats/2mg.js b/js/formats/2mg.js new file mode 100644 index 0000000..5a0cf68 --- /dev/null +++ b/js/formats/2mg.js @@ -0,0 +1,53 @@ +/* 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 DOS from './do'; +import Nibble from './nib'; +import ProDOS from './po'; + +export default function _2MG(options) { + var { rawData } = options; + var disk; + var volume = 254; + + // Standard header size is 64 bytes. Make assumptions. + var prefix = new Uint8Array(rawData.slice(0, 64)); + rawData = rawData.slice(64); + var flags = + prefix[0x10] | + (prefix[0x11] << 8) | + (prefix[0x12] << 16) | + (prefix[0x13] << 24); + var readOnly = (flags & 0x80000000) !== 0; + if ((flags & 0x10) !== 0) { + volume = flags & 0xff; + } + + options = { rawData, readOnly, volume }; + + // Check image format. + // Sure, it's really 64 bits. But only 2 are actually used. + switch (prefix[0xc]) { + case 1: // PO + disk = new ProDOS(options); + break; + case 2: // NIB + disk = new Nibble(options); + break; + case 0: // dsk + default: // Something hinky, assume 'dsk' + disk = new DOS(options); + break; + } + + + return disk; +} diff --git a/js/formats/d13.js b/js/formats/d13.js new file mode 100644 index 0000000..7413e80 --- /dev/null +++ b/js/formats/d13.js @@ -0,0 +1,44 @@ +/* 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 { explodeSector13, _D13O } from './format_utils'; + +export default function Nibble(options) { + var { data, name, rawData, volume, readOnly } = options; + var disk = { + format: 'd13', + name, + volume, + readOnly, + tracks: [], + trackMap: null, + rawTracks: null + }; + + for (var t = 0; t < 35; t++) { + var track = []; + for (var s = 0; s < 13; s++) { + var sector; + if (rawData) { + var off = (13 * t + _D13O[s]) * 256; + sector = new Uint8Array(rawData.slice(off, off + 256)); + } else { + sector = data[t][_D13O[s]]; + } + track = track.concat( + explodeSector13(volume, t, _D13O[s], sector) + ); + } + disk.tracks.push(track); + } + + return disk; +} diff --git a/js/formats/do.js b/js/formats/do.js new file mode 100644 index 0000000..f84253a --- /dev/null +++ b/js/formats/do.js @@ -0,0 +1,46 @@ +/* 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 { explodeSector16, _DO } from './format_utils'; +import { bytify } from '../util'; + +export default function DOS(options) { + var { data, name, rawData, volume, readOnly } = options; + var disk = { + format: 'dsk', + name, + volume, + readOnly, + tracks: [], + trackMap: null, + rawTracks: null + }; + + for (var t = 0; t < 35; t++) { + var track = []; + for (var s = 0; s < 16; s++) { + var _s = 15 - s; + var sector; + if (rawData) { + var off = (16 * t + _s) * 256; + sector = new Uint8Array(rawData.slice(off, off + 256)); + } else { + sector = data[t][_s]; + } + track = track.concat( + explodeSector16(volume, t, _DO[_s], sector) + ); + } + disk.tracks[t] = bytify(track); + } + + return disk; +} diff --git a/js/formats/format_utils.js b/js/formats/format_utils.js new file mode 100644 index 0000000..0b1de62 --- /dev/null +++ b/js/formats/format_utils.js @@ -0,0 +1,425 @@ +/* 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 { bytify, debug, toHex } from '../util'; + +// var DO = [0x0,0x7,0xE,0x6,0xD,0x5,0xC,0x4, +// 0xB,0x3,0xA,0x2,0x9,0x1,0x8,0xF]; +export var _DO = [ + 0x0,0xD,0xB,0x9,0x7,0x5,0x3,0x1, + 0xE,0xC,0xA,0x8,0x6,0x4,0x2,0xF +]; + +// var PO = [0x0,0x8,0x1,0x9,0x2,0xa,0x3,0xb, +// 0x4,0xc,0x5,0xd,0x6,0xe,0x7,0xf]; +export var _PO = [ + 0x0,0x2,0x4,0x6,0x8,0xa,0xc,0xe, + 0x1,0x3,0x5,0x7,0x9,0xb,0xd,0xf +]; + +// var D13O = [ +// 0x0, 0xa, 0x7, 0x4, 0x1, 0xb, 0x8, 0x5, 0x2, 0xc, 0x9, 0x6, 0x3 +// ]; + +export var _D13O = [ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc +]; + +var _trans53 = [ + 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba, + 0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb, + 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef, + 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff +]; + +var _trans62 = [ + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +]; + +export var detrans62 = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x02, 0x03, 0x00, 0x04, 0x05, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x08, + 0x00, 0x00, 0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x00, 0x00, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E, + 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x21, + 0x00, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x2A, 0x2B, + 0x00, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0x00, 0x00, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x00, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F +]; + +/** + * From Beneath Apple DOS + */ + +export function fourXfour(val) { + var xx = val & 0xaa; + var yy = val & 0x55; + + xx >>= 1; + xx |= 0xaa; + yy |= 0xaa; + + return [xx, yy]; +} + +export function defourXfour(xx, yy) { + return ((xx << 1) | 0x01) & yy; +} + +export function explodeSector16(volume, track, sector, data) { + var checksum; + + var buf = [], idx; + + var gap; + + /* + * Gap 1/3 (40/0x28 bytes) + */ + + if (sector === 0) // Gap 1 + gap = 0x80; + else { // Gap 3 + gap = track === 0 ? 0x28 : 0x26; + } + + for (idx = 0; idx < gap; idx++) { + buf.push(0xff); + } + + /* + * Address Field + */ + + checksum = volume ^ track ^ sector; + buf = buf.concat([0xd5, 0xaa, 0x96]); // Address Prolog D5 AA 96 + buf = buf.concat(fourXfour(volume)); + buf = buf.concat(fourXfour(track)); + buf = buf.concat(fourXfour(sector)); + buf = buf.concat(fourXfour(checksum)); + buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB + + /* + * Gap 2 (5 bytes) + */ + + for (idx = 0; idx < 0x05; idx++) { + buf.push(0xff); + } + + /* + * Data Field + */ + + buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD + + var nibbles = []; + var ptr2 = 0; + var ptr6 = 0x56; + var idx2, idx6; + + for (idx = 0; idx < 0x156; idx++) { + nibbles[idx] = 0; + } + + idx2 = 0x55; + for (idx6 = 0x101; idx6 >= 0; idx6--) { + var val6 = data[idx6 % 0x100]; + var val2 = nibbles[ptr2 + idx2]; + + val2 = (val2 << 1) | (val6 & 1); + val6 >>= 1; + val2 = (val2 << 1) | (val6 & 1); + val6 >>= 1; + + nibbles[ptr6 + idx6] = val6; + nibbles[ptr2 + idx2] = val2; + + if (--idx2 < 0) + idx2 = 0x55; + } + + var last = 0; + for (idx = 0; idx < 0x156; idx++) { + var val = nibbles[idx]; + buf.push(_trans62[last ^ val]); + last = val; + } + buf.push(_trans62[last]); + + buf = buf.concat([0xde, 0xaa, 0xf2]); // Epilog DE AA F2 + + /* + * Gap 3 + */ + + buf.push(0xff); + + return buf; +} + +export function explodeSector13(volume, track, sector, data) { + var checksum; + + var buf = [], idx; + + var gap; + + /* + * Gap 1/3 (40/0x28 bytes) + */ + + if (sector === 0) // Gap 1 + gap = 0x80; + else { // Gap 3 + gap = track === 0 ? 0x28 : 0x26; + } + + for (idx = 0; idx < gap; idx++) { + buf.push(0xff); + } + + /* + * Address Field + */ + + checksum = volume ^ track ^ sector; + buf = buf.concat([0xd5, 0xaa, 0xb5]); // Address Prolog D5 AA B5 + buf = buf.concat(fourXfour(volume)); + buf = buf.concat(fourXfour(track)); + buf = buf.concat(fourXfour(sector)); + buf = buf.concat(fourXfour(checksum)); + buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB + + /* + * Gap 2 (5 bytes) + */ + + for (idx = 0; idx < 0x05; idx++) { + buf.push(0xff); + } + + /* + * Data Field + */ + + buf = buf.concat([0xd5, 0xaa, 0xad]); // Data Prolog D5 AA AD + + var nibbles = []; + + var jdx = 0; + for (idx = 0x32; idx >= 0; idx--) { + var a5 = data[jdx] >> 3; + var a3 = data[jdx] & 0x07; + jdx++; + var b5 = data[jdx] >> 3; + var b3 = data[jdx] & 0x07; + jdx++; + var c5 = data[jdx] >> 3; + var c3 = data[jdx] & 0x07; + jdx++; + var d5 = data[jdx] >> 3; + var d3 = data[jdx] & 0x07; + jdx++; + var e5 = data[jdx] >> 3; + var e3 = data[jdx] & 0x07; + jdx++; + nibbles[idx + 0x00] = a5; + nibbles[idx + 0x33] = b5; + nibbles[idx + 0x66] = c5; + nibbles[idx + 0x99] = d5; + nibbles[idx + 0xcc] = e5; + nibbles[idx + 0x100] = a3 << 2 | (d3 & 0x4) >> 1 | (e3 & 0x4) >> 2; + nibbles[idx + 0x133] = b3 << 2 | (d3 & 0x2) | (e3 & 0x2) >> 1; + nibbles[idx + 0x166] = c3 << 2 | (d3 & 0x1) << 1 | (e3 & 0x1); + } + nibbles[0xff] = data[jdx] >> 3; + nibbles[0x199] = data[jdx] & 0x07; + + var val; + var last = 0; + for (idx = 0x199; idx >= 0x100; idx--) { + val = nibbles[idx]; + buf.push(_trans53[last ^ val]); + last = val; + } + for (idx = 0x0; idx < 0x100; idx++) { + val = nibbles[idx]; + buf.push(_trans53[last ^ val]); + last = val; + } + buf.push(_trans53[last]); + + buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB + + /* + * Gap 3 + */ + + buf.push(0xff); + + return buf; +} + +export function readSector(drive, track, sector) { + var _sector = cur.fmt == 'po' ? _PO[sector] : _DO[sector]; + var val, state = 0; + var idx = 0; + var retry = 0; + var cur = drive.tracks[track]; + + function _readNext() { + var result = cur[idx++]; + if (idx >= cur.length) { + idx = 0; + retry++; + } + return result; + } + function _skipBytes(count) { + idx += count; + if (idx >= cur.length) { + idx %= cur.length; + retry++; + } + } + var t = 0, s = 0, v = 0, jdx, kdx, checkSum; + var data = []; + while (retry < 4) { + switch (state) { + case 0: + val = _readNext(); + state = (val === 0xd5) ? 1 : 0; + break; + case 1: + val = _readNext(); + state = (val === 0xaa) ? 2 : 0; + break; + case 2: + val = _readNext(); + state = (val === 0x96) ? 3 : (val === 0xad ? 4 : 0); + break; + case 3: // Address + v = defourXfour(_readNext(), _readNext()); // Volume + t = defourXfour(_readNext(), _readNext()); + s = defourXfour(_readNext(), _readNext()); + checkSum = defourXfour(_readNext(), _readNext()); + if (checkSum != (v ^ t ^ s)) { + debug('Invalid header checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum)); + } + _skipBytes(3); // Skip footer + state = 0; + break; + case 4: // Data + if (s === _sector && t === track) { + var data2 = []; + var last = 0; + for (jdx = 0x55; jdx >= 0; jdx--) { + val = detrans62[_readNext() - 0x80] ^ last; + data2[jdx] = val; + last = val; + } + for (jdx = 0; jdx < 0x100; jdx++) { + val = detrans62[_readNext() - 0x80] ^ last; + data[jdx] = val; + last = val; + } + checkSum = detrans62[_readNext() - 0x80] ^ last; + if (checkSum) { + debug('Invalid data checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum)); + } + for (kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) { + data[kdx] <<= 1; + if ((data2[jdx] & 0x01) !== 0) { + data[kdx] |= 0x01; + } + data2[jdx] >>= 1; + + data[kdx] <<= 1; + if ((data2[jdx] & 0x01) !== 0) { + data[kdx] |= 0x01; + } + data2[jdx] >>= 1; + + if (--jdx < 0) jdx = 0x55; + } + return data; + } + else + _skipBytes(0x159); // Skip data, checksum and footer + state = 0; + break; + default: + break; + } + } + return []; +} + +export function jsonEncode(cur, pretty) { + var data = []; + var format = 'dsk'; + for (var t = 0; t < cur.tracks.length; t++) { + data[t] = []; + if (cur.format === 'nib') { + format = 'nib'; + data[t] = base64_encode(cur.tracks[t]); + } else { + for (var s = 0; s < 0x10; s++) { + data[t][s] = base64_encode(readSector(cur, t)); + } + } + } + return JSON.stringify({ + 'type': format, + 'encoding': 'base64', + 'volume': cur.volume, + 'data': data + }, null, pretty ? ' ' : null); +} + +export function jsonDecode(data) { + var cur = {}; + var tracks = []; + var json = JSON.parse(data); + var v = json.volume; + for (var t = 0; t < json.data.length; t++) { + var track = []; + for (var s = 0; s < json.data[t].length; s++) { + var _s = 15 - s; + var d = base64_decode(json.data[t][_s]); + track = track.concat(explodeSector16(v, t, s, d)); + } + tracks[t] = bytify(track); + } + cur.volume = v; + cur.format = json.type; + cur.tracks = tracks; + cur.trackMap = null; + + return cur; +} diff --git a/js/formats/nib.js b/js/formats/nib.js new file mode 100644 index 0000000..dee4e1d --- /dev/null +++ b/js/formats/nib.js @@ -0,0 +1,36 @@ +/* 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. + */ + +export default function Nibble(options) { + var { data, name, rawData, volume, readOnly } = options; + var disk = { + format: 'nib', + name, + volume: volume || 254, + readOnly: readOnly || false, + tracks: [], + trackMap: null, + rawTracks: null + }; + + for (var t = 0; t < 35; t++) { + var track; + if (rawData) { + var off = t * 0x1a00; + track = new Uint8Array(data.slice(off, off + 0x1a00)); + } else { + track = data[t]; + } + disk.tracks[t] = track; + } + + return disk; +} diff --git a/js/formats/po.js b/js/formats/po.js new file mode 100644 index 0000000..f7a025b --- /dev/null +++ b/js/formats/po.js @@ -0,0 +1,45 @@ +/* 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 { explodeSector16, _PO } from './format_utils'; +import { bytify } from '../util'; + +export default function ProDOS(options) { + var { data, name, rawData, volume, readOnly } = options; + var disk = { + format: 'nib', + name, + volume: volume || 254, + tracks: [], + readOnly: readOnly || false, + trackMap: null, + rawTracks: null + }; + + for (var t = 0; t < 35; t++) { + var track = []; + for (var s = 0; s < 16; s++) { + var sector; + if (rawData) { + var off = (16 * t + s) * 256; + sector = new Uint8Array(rawData.slice(off, off + 256)); + } else { + sector = data[t][s]; + } + track = track.concat( + explodeSector16(volume, t, _PO[s], sector) + ); + } + disk.tracks[t] = bytify(track); + } + + return disk; +} diff --git a/js/formats/woz.js b/js/formats/woz.js new file mode 100644 index 0000000..5c98e9d --- /dev/null +++ b/js/formats/woz.js @@ -0,0 +1,276 @@ +/* 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 { debug, toHex } from '../util'; + +var WOZ_HEADER_START = 0; +var WOZ_HEADER_SIZE = 12; + +var WOZ1_SIGNATURE = 0x315A4F57; +var WOZ2_SIGNATURE = 0x325A4F57; +var WOZ_INTEGRITY_CHECK = 0x0a0d0aff; + +function stringFromBytes(data, start, end) { + return String.fromCharCode.apply( + null, + new Uint8Array(data.buffer.slice(data.byteOffset + start, data.byteOffset + end)) + ); +} + +function grabNibble(bits, offset) { + var nibble = 0; + var waitForOne = true; + + while (offset < bits.length) { + var bit = bits[offset]; + if (bit) { + nibble = (nibble << 1) | 0x01; + waitForOne = false; + } else { + if (!waitForOne) { + nibble = nibble << 1; + } + } + if (nibble & 0x80) { + // nibble complete return it + break; + } + offset += 1; + } + + return { + nibble: nibble, + offset: offset + }; +} + +function InfoChunk(data) { + Object.assign(this, { + version: data.getUint8(0), + diskType: data.getUint8(1), + writeProtected: data.getUint8(2), + synchronized: data.getUint8(3), + cleaned: data.getUint8(4), + creator: stringFromBytes(data, 5, 37) + }); + + if (this.version === 2) { + Object.assign(this, { + sides: data.getUint8(37), + bootSector: data.getUint8(38), + bitTiming: data.getUint8(39), + compatibleHardware: data.getUint16(40, true), + requiredRAM: data.getUint16(42, true), + largestTrack: data.getUint16(44, true) + }); + } + + return this; +} + +function TMapChunk(data) { + this.trackMap = []; + + for (var idx = 0; idx < 160; idx++) { + this.trackMap.push(data.getUint8(idx)); + } + + return this; +} + +function TrksChunk(data) { + var WOZ_TRACK_SIZE = 6656; + var WOZ_TRACK_INFO_BITS = 6648; + + this.rawTracks = []; + this.tracks = []; + for (var trackNo = 0, idx = 0; idx < data.byteLength; idx += WOZ_TRACK_SIZE, trackNo++) { + var jdx; + var track = []; + var rawTrack = []; + var slice = data.buffer.slice(data.byteOffset + idx, data.byteOffset + idx + WOZ_TRACK_SIZE); + var trackData = new Uint8Array(slice); + var trackInfo = new DataView(slice); + var trackBitCount = trackInfo.getUint16(WOZ_TRACK_INFO_BITS, true); + for (jdx = 0; jdx < trackBitCount; jdx++) { + var byteIndex = jdx >> 3; + var bitIndex = 7 - (jdx & 0x07); + rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x1; + } + + track = []; + var offset = 0; + while (offset < rawTrack.length) { + var result = grabNibble(rawTrack, offset); + if (!result.nibble) { break; } + track.push(result.nibble); + offset = result.offset + 1; + } + + this.tracks[trackNo] = track; + this.rawTracks[trackNo] = rawTrack; + } + + return this; +} + +function TrksChunk2(data) { + var trackNo; + this.trks = []; + for (trackNo = 0; trackNo < 160; trackNo++) { + var startBlock = data.getUint16(trackNo * 8, true); + var blockCount = data.getUint16(trackNo * 8 + 2, true); + var bitCount = data.getUint32(trackNo * 8 + 4, true); + if (bitCount === 0) { break; } + this.trks.push({ + startBlock: startBlock, + blockCount: blockCount, + bitCount: bitCount + }); + } + this.tracks = []; + this.rawTracks = []; + + var bits = data.buffer; + for (trackNo = 0; trackNo < this.trks.length; trackNo++) { + var trk = this.trks[trackNo]; + var track = []; + var rawTrack = []; + var start = trk.startBlock * 512; + var end = start + trk.blockCount * 512; + var slice = bits.slice(start, end); + var trackData = new Uint8Array(slice); + for (var jdx = 0; jdx < trk.bitCount; jdx++) { + var byteIndex = jdx >> 3; + var bitIndex = 7 - (jdx & 0x07); + rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x1; + } + + track = []; + var offset = 0; + while (offset < rawTrack.length) { + var result = grabNibble(rawTrack, offset); + if (!result.nibble) { break; } + track.push(result.nibble); + offset = result.offset + 1; + } + + this.tracks[trackNo] = track; + this.rawTracks[trackNo] = rawTrack; + } + + return this; +} + +function MetaChunk(data) { + var infoStr = stringFromBytes(data, 0, data.byteLength); + var parts = infoStr.split('\n'); + var info = parts.reduce(function(acc, part) { + var subParts = part.split('\t'); + acc[subParts[0]] = subParts[1]; + return acc; + }, {}); + + Object.assign(this, info); + + return this; +} + +export default function Woz(options) { + var { rawData } = options; + var dv = new DataView(rawData, 0); + var dvOffset = 0; + var disk = { + format: 'woz' + }; + + var wozVersion; + var chunks = {}; + + function readHeader() { + var wozSignature = dv.getUint32(WOZ_HEADER_START + 0, true); + + switch (wozSignature) { + case WOZ1_SIGNATURE: + wozVersion = 1; + break; + case WOZ2_SIGNATURE: + wozVersion = 2; + break; + default: + return false; + } + + if (dv.getUint32(WOZ_HEADER_START + 4, true) !== WOZ_INTEGRITY_CHECK) { + return false; + } + + return true; + } + + function readChunk() { + if (dvOffset >= dv.byteLength) { + return null; + } + + var type = dv.getUint32(dvOffset, true); + var size = dv.getUint32(dvOffset + 4, true); + var data = new DataView(dv.buffer, dvOffset + 8, size); + dvOffset += size + 8; + + return { + type: type, + size: size, + data: data + }; + } + + if (readHeader()) { + dvOffset = WOZ_HEADER_SIZE; + var chunk = readChunk(); + while (chunk) { + switch (chunk.type) { + case 0x4F464E49: // INFO + chunks.info = new InfoChunk(chunk.data); + break; + case 0x50414D54: // TMAP + chunks.tmap = new TMapChunk(chunk.data); + break; + case 0x534B5254: // TRKS + if (wozVersion === 1) { + chunks.trks = new TrksChunk(chunk.data); + } else { + chunks.trks = new TrksChunk2(chunk.data); + } + break; + case 0x4154454D: // META + chunks.meta = new MetaChunk(chunk.data); + break; + case 0x54495257: // WRIT + // Ignore + break; + default: + debug('Unsupported chunk', toHex(chunk.type, 8)); + } + chunk = readChunk(); + } + } + + debug(chunks); + + disk.trackMap = chunks.tmap.trackMap; + disk.tracks = chunks.trks.tracks; + disk.rawTracks = chunks.trks.rawTracks; + disk.readOnly = true; //chunks.info.writeProtected === 1; + disk.name = chunks.info.title; + + return disk; +} diff --git a/js/main2.js b/js/main2.js index d656892..86b29c9 100644 --- a/js/main2.js +++ b/js/main2.js @@ -80,12 +80,12 @@ export function openSave(drive, event) var blob = new Blob([data], { 'type': mimetype }); a.href = window.URL.createObjectURL(blob); - a.download = drivelights.label(drive) + '.dsk'; + a.download = driveLights.label(drive) + '.dsk'; if (event.metaKey) { dumpDisk(drive); } else { - document.querySelector('#save_name').value = drivelights.label(drive); + document.querySelector('#save_name').value = driveLights.label(drive); MicroModal.show('save-modal'); } } @@ -230,7 +230,7 @@ function doLoadLocalDisk(drive, file) { var ext = parts.pop().toLowerCase(); var name = parts.join('.'); if (disk2.setBinary(drive, name, ext, this.result)) { - drivelights.label(drive, name); + driveLights.label(drive, name); initGamepad(); } }; @@ -251,7 +251,7 @@ function doLoadHTTP(drive, _url) { var ext = fileParts.pop().toLowerCase(); var name = decodeURIComponent(fileParts.join('.')); if (disk2.setBinary(drive, name, ext, req.response)) { - drivelights.label(drive, name); + driveLights.label(drive, name); MicroModal.close('http-modal'); initGamepad(); } @@ -338,7 +338,7 @@ var vm = new VideoModes(gr, hgr, gr2, hgr2, false); vm.multiScreen(multiScreen); var dumper = new ApplesoftDump(cpu); -var drivelights = new DriveLights(); +var driveLights = new DriveLights(); var io = new Apple2IO(cpu, vm); var keyboard = new KeyBoard(cpu, io); var audio = new Audio(io); @@ -349,7 +349,7 @@ var lc = new LanguageCard(io, 0, rom); var parallel = new Parallel(io, 1, printer); var slinky = new RAMFactor(io, 2, 1024 * 1024); var videoterm = new Videoterm(io, 3, context1); -var disk2 = new DiskII(io, 6, drivelights); +var disk2 = new DiskII(io, 6, driveLights); var clock = new Thunderclock(io, 7); cpu.addPageHandler(ram1); @@ -411,7 +411,7 @@ export function updateSound() { function dumpDisk(drive) { var wind = window.open('', '_blank'); - wind.document.title = drivelights.label(drive); + wind.document.title = driveLights.label(drive); wind.document.write('
');
     wind.document.write(disk2.getJSON(drive, true));
     wind.document.write('
'); @@ -565,7 +565,7 @@ function saveState() { lc: lc.getState(), vm: vm.getState(), disk2: disk2.getState(), - drivelights: drivelights.getState() + driveLights: driveLights.getState() }; if (slinky) { state.slinky = slinky.getState(); @@ -591,7 +591,7 @@ function restoreState() { lc.setState(state.lc); vm.setState(state.vm); disk2.setState(state.disk2); - drivelights.setState(state.drivelights); + driveLights.setState(state.driveLights); if (slinky && state.slinky) { slinky.setState(state.slinky); } @@ -645,7 +645,7 @@ function loadDisk(drive, disk) { disk_cur_cat[drive] = category; disk_cur_name[drive] = name; - drivelights.label(drive, name); + driveLights.label(drive, name); disk2.setDisk(drive, disk); initGamepad(disk.gamepad); } @@ -697,8 +697,8 @@ function saveLocalStorage(drive, name) { window.alert('Saved'); - drivelights.label(drive, name); - drivelights.dirty(drive, false); + driveLights.label(drive, name); + driveLights.dirty(drive, false); updateLocalStorage(); } @@ -716,8 +716,8 @@ function loadLocalStorage(drive, name) { var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}'); if (diskIndex[name]) { disk2.setJSON(drive, diskIndex[name]); - drivelights.label(drive, name); - drivelights.dirty(drive, false); + driveLights.label(drive, name); + driveLights.dirty(drive, false); } } diff --git a/js/main2e.js b/js/main2e.js index 37032b8..a029ecd 100644 --- a/js/main2e.js +++ b/js/main2e.js @@ -75,12 +75,12 @@ export function openSave(drive, event) var blob = new Blob([data], { 'type': mimetype }); a.href = window.URL.createObjectURL(blob); - a.download = drivelights.label(drive) + '.dsk'; + a.download = driveLights.label(drive) + '.dsk'; if (event.metaKey) { dumpDisk(drive); } else { - document.querySelector('#save_name').value = drivelights.label(drive); + document.querySelector('#save_name').value = driveLights.label(drive); MicroModal.show('save-modal'); } } @@ -226,7 +226,7 @@ function doLoadLocalDisk(drive, file) { var ext = parts.pop().toLowerCase(); var name = parts.join('.'); if (disk2.setBinary(drive, name, ext, this.result)) { - drivelights.label(drive, name); + driveLights.label(drive, name); initGamepad(); } }; @@ -247,7 +247,7 @@ function doLoadHTTP(drive, _url) { var ext = fileParts.pop().toLowerCase(); var name = decodeURIComponent(fileParts.join('.')); if (disk2.setBinary(drive, name, ext, req.response)) { - drivelights.label(drive, name); + driveLights.label(drive, name); MicroModal.close('http-modal'); initGamepad(); } @@ -323,7 +323,7 @@ vm.enhanced(enhanced); vm.multiScreen(multiScreen); var dumper = new ApplesoftDump(cpu); -var drivelights = new DriveLights(); +var driveLights = new DriveLights(); var io = new Apple2IO(cpu, vm); var keyboard = new KeyBoard(cpu, io, true); var audio = new Audio(io); @@ -336,7 +336,7 @@ cpu.addPageHandler(mmu); var parallel = new Parallel(io, 1, printer); var slinky = new RAMFactor(io, 2, 1024 * 1024); -var disk2 = new DiskII(io, 6, drivelights); +var disk2 = new DiskII(io, 6, driveLights); var clock = new Thunderclock(io, 7); io.setSlot(1, parallel); @@ -386,7 +386,7 @@ export function updateSound() { function dumpDisk(drive) { var wind = window.open('', '_blank'); - wind.document.title = drivelights.label(drive); + wind.document.title = driveLights.label(drive); wind.document.write('
');
     wind.document.write(disk2.getJSON(drive, true));
     wind.document.write('
'); @@ -534,7 +534,7 @@ function saveState() { mmu: mmu.getState(), vm: vm.getState(), disk2: disk2.getState(), - drivelights: drivelights.getState() + driveLights: driveLights.getState() }; if (slinky) { state.slinky = slinky.getState(); @@ -557,7 +557,7 @@ function restoreState() { mmu.setState(state.mmu); vm.setState(state.vm); disk2.setState(state.disk2); - drivelights.setState(state.drivelights); + driveLights.setState(state.driveLights); if (slinky && state.slinky) { slinky.setState(state.slinky); } @@ -611,7 +611,7 @@ function loadDisk(drive, disk) { disk_cur_cat[drive] = category; disk_cur_name[drive] = name; - drivelights.label(drive, name); + driveLights.label(drive, name); disk2.setDisk(drive, disk); initGamepad(disk.gamepad); } @@ -663,8 +663,8 @@ function saveLocalStorage(drive, name) { window.alert('Saved'); - drivelights.label(drive, name); - drivelights.dirty(drive, false); + driveLights.label(drive, name); + driveLights.dirty(drive, false); updateLocalStorage(); } @@ -682,8 +682,8 @@ function loadLocalStorage(drive, name) { var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}'); if (diskIndex[name]) { disk2.setJSON(drive, diskIndex[name]); - drivelights.label(drive, name); - drivelights.dirty(drive, false); + driveLights.label(drive, name); + driveLights.dirty(drive, false); } }