/* 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 = drive.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, s)); } } } 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; }