2019-10-02 02:56:10 +00:00
|
|
|
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
|
|
* the above copyright notice appear in all copies and that both that
|
|
|
|
* copyright notice and this permission notice appear in supporting
|
|
|
|
* documentation. No representations are made about the suitability of this
|
|
|
|
* software for any purpose. It is provided "as is" without express or
|
|
|
|
* implied warranty.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { debug, 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) {
|
2020-11-26 01:28:37 +00:00
|
|
|
case WOZ1_SIGNATURE:
|
|
|
|
wozVersion = 1;
|
|
|
|
break;
|
|
|
|
case WOZ2_SIGNATURE:
|
|
|
|
wozVersion = 2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false;
|
2019-10-02 02:56:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-11-26 01:28:37 +00:00
|
|
|
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
|
2019-10-02 02:56:10 +00:00
|
|
|
// Ignore
|
2020-11-26 01:28:37 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
debug('Unsupported chunk', toHex(chunk.type, 8));
|
2019-10-02 02:56:10 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|