Crude native woz support. (#13)

Readonly for now.
This commit is contained in:
Will Scullin 2019-10-01 19:56:10 -07:00 committed by GitHub
parent 7d9090133b
commit 3b95726655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1692 additions and 585 deletions

View File

@ -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

425
js/cards/smartport.js Normal file
View File

@ -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);
}
};
}

53
js/formats/2mg.js Normal file
View File

@ -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;
}

44
js/formats/d13.js Normal file
View File

@ -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;
}

46
js/formats/do.js Normal file
View File

@ -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;
}

425
js/formats/format_utils.js Normal file
View File

@ -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;
}

36
js/formats/nib.js Normal file
View File

@ -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;
}

45
js/formats/po.js Normal file
View File

@ -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;
}

276
js/formats/woz.js Normal file
View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}