parent
7d9090133b
commit
3b95726655
2
LICENSE
2
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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,425 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { base64_decode } 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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/* 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 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;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* 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 { 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;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* 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 { 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;
|
||||
}
|
|
@ -0,0 +1,425 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { 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;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/* 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 { 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;
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
/* 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) {
|
||||
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;
|
||||
}
|
28
js/main2.js
28
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('<pre>');
|
||||
wind.document.write(disk2.getJSON(drive, true));
|
||||
wind.document.write('</pre>');
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
28
js/main2e.js
28
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('<pre>');
|
||||
wind.document.write(disk2.getJSON(drive, true));
|
||||
wind.document.write('</pre>');
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue