waves/public/assets/g/commodoreclicker/js/c64/disk.js
2025-04-09 17:11:14 -05:00

487 lines
19 KiB
JavaScript

define(function() {
return {
IEC_ID: 8,
iec: {
state: null,
statetime: null,
eoi: null,
prevclk: null,
prevatn: null,
data: null,
bitpos: null,
dataready: null,
datalast: null
},
listening: false,
talking: false,
currCommand: null,
currChannel: null,
currFilename: [],
currFileEntry: null,
currFilePos: null,
diskData: null,
directory: [],
sectorPositions: [
0, 21, 42, 63, 84,
105, 126, 147, 168, 189,
210, 231, 252, 273, 294,
315, 336, 357, 376, 395,
414, 433, 452, 471, 490,
508, 526, 544, 562, 580,
598, 615, 632, 649, 666
],
load: function(data) {
this.diskData = new Uint8Array(data);
var i, j, pos, track, sector, content, res, file;
// Ignore the BAM for now, since we can't save
// Read the directory
track = 18; sector = 1;
do {
res = this.readSector(track, sector, true);
track = res.nextTrack;
sector = res.nextSector;
content = res.content;
for (i = 0; i < 8; i++) {
pos = i * 32;
file = {
type: content[pos + 2] & 15,
locked: !!(content[pos + 2] & 64),
closed: !!(content[pos + 2] & 128),
startTrack: content[pos + 3],
startSector: content[pos + 4],
name: new Uint8Array(16),
sectorCount: content[pos + 30] + (content[pos + 31] * 256)
};
// We have to assume the size is valid, but it shouldn't
// be egregiously oversized
file.length = file.sectorCount * 254;
if (file.sectorCount < 1024) {
file.data = new Uint8Array(file.length);
}
for (j = 0; j < 16; j++) {
if (content[pos + 5 + j] != 0xA0) {
file.name[j] = content[pos + 5 + j];
}
}
if (file.startTrack) {
this.directory.push(file);
}
}
} while (track);
for (i = 0; i < this.directory.length; i++) {
track = this.directory[i].startTrack;
sector = this.directory[i].startSector;
pos = 0;
do {
res = this.readSector(track, sector);
track = res.nextTrack;
sector = res.nextSector;
for (j = 0; j < 254; j++) {
this.directory[i].data[pos++] = res.content[j];
}
} while (track);
}
},
readSector: function(track, sector, padTop) {
var pos = (this.sectorPositions[track - 1] + sector) * 256;
var i, len, content = [], nextTrack, nextSector;
nextTrack = this.diskData[pos++];
nextSector = this.diskData[pos++];
if (padTop) {
pos -= 2;
len = 256;
} else {
len = 254;
}
for (i = 0; i < len; i++, pos++) {
content[i] = this.diskData[pos];
}
return {
nextTrack: nextTrack,
nextSector: nextSector,
content: content
};
},
command: function() {
switch (this.iec.data & 0xF0) {
case 0x20:
if ((this.iec.data & 0x0F) == this.IEC_ID) {
this.listening = true;
this.currCommand = '';
}
break;
case 0x30:
this.listening = false;
this.currCommand = '';
break;
case 0x40:
if ((this.iec.data & 0x0F) == this.IEC_ID) {
this.talking = true;
}
break;
case 0x50:
this.talking = false;
break;
case 0x60:
// DATA
this.currCommand = 'DATA';
if (this.talking) {
// Sending a file to the computer; which file?
if (this.currFilename.length == 1 && this.currFilename[0] == 42) {
// *: Load first file
this.currFileEntry = 0;
} else {
this.currFileEntry = -1;
for (i = 0; i < this.directory.length; i++) {
for (j = 0, m = 0; j < this.currFilename.length; j++) {
if (this.currFilename[j] == this.directory[i].name[j]) {
m++;
}
}
if (m == this.currFilename.length) {
this.currFileEntry = i;
break;
}
}
// If still -1, the file was not found
}
}
break;
case 0xE0:
// CLOSE
this.currCommand = 'CLOSE';
break;
case 0xF0:
// OPEN
if (this.listening) {
this.currCommand = 'OPEN';
this.currChannel = this.iec.data & 0x0F;
this.currFilename.length = 0;
this.currFilePos = 0;
}
break;
}
},
recv: function() {
var i, j, m;
switch (this.currCommand) {
case 'OPEN':
this.currFilename.push(this.iec.data);
break;
}
},
send: function() {
if (this.talking) {
// Sending file data (if the file exists)
if (
this.currFileEntry !== null && this.currFileEntry != -1 &&
this.currFilePos < this.directory[this.currFileEntry].length
) {
this.iec.dataready = true;
this.iec.data = this.directory[this.currFileEntry].data[this.currFilePos++];
this.iec.datalast = (this.currFilePos == this.directory[this.currFileEntry].length);
} else {
this.iec.dataready = false;
}
}
},
getFileProgress: function() {
if (this.talking) {
if (
this.currFileEntry !== null && this.currFileEntry != -1 &&
this.currFilePos < this.directory[this.currFileEntry].length
) {
return (this.currFilePos * 100) / this.directory[this.currFileEntry].length;
}
}
return 0;
},
getState: function() {
return {
iec: $.extend({}, this.iec),
listening: this.listening,
talking: this.talking,
currCommand: this.currCommand,
currChannel: this.currChannel,
currFilename: this.currFilename.slice(0),
currFilePos: this.currFilePos
};
},
setState: function(state) {
this.iec = $.extend({}, state.iec);
this.listening = state.listening;
this.talking = state.talking;
this.currCommand = state.currCommand;
this.currChannel = state.currChannel;
this.currFilename = state.currFilename.slice(0);
this.currFilePos = state.currFilePos;
},
step: function() {
var val, prevState = this.iec.state;
this.iec.statetime++;
if (!this.iec.prevatn && this.owner.IEC.check('ATN')) {
// Hey! Hey disk! Hey disk, hey!
this.iec.prevatn = true;
this.iec.state = 16;
this.iec.statetime = 0;
return;
}
switch (this.iec.state) {
// Initial state: waiting for talker
case 0:
if (this.iec.prevclk && !this.owner.IEC.check('CLK')) {
// Talker has indicated Ready to Send
// We should indicate Ready to Listen
this.owner.IEC.log(this.IEC_ID, 'received Ready-to-send');
this.iec.state = 1;
}
break;
// About to indicate ready-to-listen
case 1:
if (this.iec.statetime >= 60) {
this.owner.IEC.log(this.IEC_ID, 'indicates Ready-to-listen');
this.owner.IEC.release(this.IEC_ID, 'DATA');
if (this.owner.IEC.check('DATA')) {
// DATA is still pulled after we released it
// Talker is indicating it wishes to turnaround
this.owner.IEC.log(this.IEC_ID, 'received turnaround');
this.iec.state = 6;
this.owner.IEC.pulldown(this.IEC_ID, 'CLK');
} else {
this.iec.state = 2;
}
}
break;
// Awaiting data and/or EOI
case 2:
if (this.owner.IEC.check('CLK')) {
// Talker will proceed to send
this.owner.IEC.log(this.IEC_ID, 'awaiting data');
this.iec.prevclk = true;
this.iec.state = 3;
this.iec.data = 0;
this.iec.bitpos = 0;
} else if (!this.iec.eoi && this.iec.statetime >= 200) {
// Over 200us have passed without CLK going high
// Talker is indicating End of Indicator
this.owner.IEC.log(this.IEC_ID, 'entering EOI');
this.owner.IEC.pulldown(this.IEC_ID, 'DATA');
this.iec.state = 7;
}
break;
// Data reception
case 3:
if (this.iec.prevclk && !this.owner.IEC.check('CLK')) {
// CLK rising edge, pull a bit
// Note that data levels are reversed
val = this.owner.IEC.check('DATA')
? 0
: (1 << this.iec.bitpos);
this.owner.IEC.log(this.IEC_ID, 'got bit '+val);
this.iec.data |= val;
} else if (!this.iec.prevclk && this.owner.IEC.check('CLK')) {
// CLK falling edge, advance a bit
this.iec.bitpos++;
this.owner.IEC.log(this.IEC_ID, 'advancing');
if (this.iec.bitpos == 8) {
// Data is complete
this.owner.IEC.log(this.IEC_ID, 'received data '+this.iec.data);
this.iec.state = 4;
}
}
this.iec.prevclk = this.owner.IEC.check('CLK');
break;
// End-of-byte ack
case 4:
if (this.iec.statetime >= 60) {
// Acknowledge end of data, act thereon
this.owner.IEC.pulldown(this.IEC_ID, 'DATA');
this.iec.state = 5;
if (this.owner.IEC.check('ATN')) {
// Command issued
this.owner.IEC.log(this.IEC_ID, 'command '+this.iec.data);
this.command();
} else {
this.owner.IEC.log(this.IEC_ID, 'data '+this.iec.data);
this.recv();
}
}
break;
// Post-byte timeout
case 5:
if (this.iec.statetime >= 20) {
this.iec.state = 0;
}
break;
// Post-turnaround timeout
case 6:
if (this.iec.statetime >= 80) {
this.owner.IEC.log(this.IEC_ID, 'turned around');
this.owner.IEC.release(this.IEC_ID, 'DATA');
this.iec.state = 8;
}
break;
// EOI ack
case 7:
if (this.iec.statetime >= 60) {
// EOI has been acknowledged for 60us, await data
this.owner.IEC.log(this.IEC_ID, 'acknowledged EOI');
this.owner.IEC.release(this.IEC_ID, 'DATA');
this.iec.state = 2;
this.iec.eoi = true;
}
break;
case 8:
if (this.iec.statetime >= 60) {
this.send();
if (this.iec.dataready) {
// Ready to send, await acknowledgement
this.owner.IEC.log(this.IEC_ID, 'ready to send');
this.owner.IEC.release(this.IEC_ID, 'CLK');
this.iec.state = 9;
}
}
break;
case 9:
this.iec.dataready = false;
if (!this.owner.IEC.check('DATA')) {
// Ack'd, indicate EOI if necessary
if (this.iec.datalast) {
// Refuse to start data until EOI ack'd
this.owner.IEC.log(this.IEC_ID, 'awaiting EOI ack');
this.iec.state = 14;
this.iec.bitpos = 0;
} else {
// Ready to fire
if (this.iec.statetime >= 80) {
this.owner.IEC.log(this.IEC_ID, 'sending byte '+this.currFilePos + ' of '+this.directory[0].length);
this.owner.IEC.pulldown(this.IEC_ID, 'CLK');
this.iec.state = 10;
this.iec.bitpos = 0;
}
}
}
break;
case 10:
// Sending one bit
this.iec.datalast = false;
if (this.iec.statetime >= 60) {
val = (this.iec.data & (1 << this.iec.bitpos));
this.owner.IEC.log(this.IEC_ID, 'sending '+val+' at bit '+this.iec.bitpos);
this.owner.IEC[val ? 'release' : 'pulldown'](this.IEC_ID, 'DATA');
this.iec.state = 11;
this.iec.bitpos++;
}
break;
case 11:
// Bit ready for latching
if (this.iec.statetime >= 60) {
this.owner.IEC.release(this.IEC_ID, 'CLK');
this.iec.state = 12;
}
break;
case 12:
// Signalling end of bit
if (this.iec.statetime >= 60) {
this.owner.IEC.pulldown(this.IEC_ID, 'CLK');
this.owner.IEC.release(this.IEC_ID, 'DATA');
this.iec.state = (this.iec.bitpos < 8) ? 10 : 13;
}
break;
case 13:
if (this.iec.statetime >= 20) {
if (this.owner.IEC.check('DATA')) {
this.owner.IEC.log(this.IEC_ID, 'byte acknowledged');
this.iec.state = 8;
if (this.currFilePos == this.directory[0].length) {
// Turnaround back to listening
this.owner.IEC.log(this.IEC_ID, 'turning back around');
this.owner.IEC.release(this.IEC_ID, 'CLK');
this.owner.IEC.pulldown(this.IEC_ID, 'DATA');
this.iec.state = 0;
}
}
}
break;
case 14:
// EOI handshake, step 1: data high
if (this.owner.IEC.check('DATA')) {
this.iec.state = 15;
}
break;
case 15:
// EOI handshake, step 2: data low again
if (!this.owner.IEC.check('DATA')) {
this.owner.IEC.pulldown(this.IEC_ID, 'CLK');
this.iec.state = 10;
this.iec.bitpos = 0;
}
break;
// ATN pulled down by the computer
case 16:
if (this.iec.statetime >= 60) {
this.atnReset();
}
break;
}
if (this.iec.state != prevState) {
this.iec.statetime = 0;
}
this.iec.prevatn = this.owner.IEC.check('ATN');
this.iec.prevclk = this.owner.IEC.check('CLK');
},
atnReset: function() {
this.owner.IEC.register(this.IEC_ID);
this.owner.IEC.pulldown(this.IEC_ID, 'DATA');
this.iec.state = 0;
this.iec.statetime = 0;
this.iec.eoi = false;
this.iec.data = 0;
this.iec.bitpos = 0;
this.iec.dataready = false;
this.iec.datalast = false;
},
reset: function() {
this.atnReset();
this.iec.prevclk = true;
this.listening = false;
this.talking = false;
this.currCommand = null;
this.currChannel = null;
this.currFilename.length = 0;
//this.directory.length = 0;
},
init: function() {
}
};
});