Port language card and smartport to TypeScript (#62)

Port language card and smartport to TypeScript.
This commit is contained in:
Will Scullin 2021-03-13 13:18:32 -08:00 committed by GitHub
parent bf483e1178
commit a3c7dd136b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 754 additions and 733 deletions

View File

@ -12,7 +12,7 @@
import { base64_decode, base64_encode} from '../base64'; import { base64_decode, base64_encode} from '../base64';
import { bit, byte, Card, DiskFormat, MemberOf, memory, nibble, rom } from '../types'; import { bit, byte, Card, DiskFormat, MemberOf, memory, nibble, rom } from '../types';
import { debug, toHex } from '../util'; import { debug, toHex } from '../util';
import { Disk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils'; import { Disk, JSONDisk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2'; import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2';
@ -337,7 +337,7 @@ export default class DiskII implements Card {
this.init(); this.init();
} }
private debug(..._args: any) { private debug(..._args: any[]) {
// debug.apply(this, arguments); // debug.apply(this, arguments);
} }
@ -728,8 +728,8 @@ export default class DiskII implements Card {
/** Sets the data for `drive` from `disk`, which is expected to be JSON. */ /** Sets the data for `drive` from `disk`, which is expected to be JSON. */
// TODO(flan): This implementation is not very safe. // TODO(flan): This implementation is not very safe.
setDisk(drive: DriveNumber, disk: any) { setDisk(drive: DriveNumber, disk: JSONDisk) {
const fmt = disk.type as DiskFormat; const fmt = disk.type;
const readOnly = disk.readOnly; const readOnly = disk.readOnly;
const name = disk.name; const name = disk.name;
@ -795,7 +795,7 @@ export default class DiskII implements Card {
return jsonEncode(cur, pretty); return jsonEncode(cur, pretty);
} }
setJSON(drive: DriveNumber, data: any) { setJSON(drive: DriveNumber, data: string) {
const cur = this.drives[drive - 1]; const cur = this.drives[drive - 1];
Object.assign(cur, jsonDecode(data)); Object.assign(cur, jsonDecode(data));
return true; return true;

View File

@ -1,210 +0,0 @@
/* 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 RAM from '../ram';
import { debug } from '../util';
export default function LanguageCard(io, rom) {
var _rom = rom;
var _bank1 = null;
var _bank2 = null;
var _ram = null;
var _readbsr = false;
var _writebsr = false;
var _bsr2 = false;
var _prewrite = false;
var _read1 = null;
var _read2 = null;
var _write1 = null;
var _write2 = null;
function _init() {
debug('Language card');
_bank1 = new RAM(0xd0, 0xdf);
_bank2 = new RAM(0xd0, 0xdf);
_ram = new RAM(0xe0, 0xff);
_write1 = _rom;
_write2 = _rom;
_read1 = _rom;
_read2 = _rom;
}
function _debug() {
// debug.apply(null, arguments);
}
_init();
var LOC = {
// Bank 2
READBSR2: 0x80,
WRITEBSR2: 0x81,
OFFBSR2: 0x82,
READWRBSR2: 0x83,
// Bank 1
READBSR1: 0x88,
WRITEBSR1: 0x89,
OFFBSR1: 0x8a,
READWRBSR1: 0x8b,
};
function _updateBanks() {
if (_readbsr) {
_read1 = _bsr2 ? _bank2 : _bank1;
_read2 = _ram;
} else {
_read1 = _rom;
_read2 = _rom;
}
if (_writebsr) {
_write1 = _bsr2 ? _bank2 : _bank1;
_write2 = _ram;
} else {
_write1 = rom;
_write2 = rom;
}
}
function _access(off, val) {
var readMode = val === undefined;
var result = 0;
switch (off & 0x8B) {
case LOC.READBSR2: // 0xC080
_readbsr = true;
_writebsr = false;
_bsr2 = true;
_prewrite = false;
_debug('Bank 2 Read');
break;
case LOC.WRITEBSR2: // 0xC081
_readbsr = false;
if (readMode) {
_writebsr = _prewrite;
}
_bsr2 = true;
_prewrite = readMode;
_debug('Bank 2 Write');
break;
case LOC.OFFBSR2: // 0xC082
_readbsr = false;
_writebsr = false;
_bsr2 = true;
_prewrite = false;
_debug('Bank 2 Off');
break;
case LOC.READWRBSR2: // 0xC083
_readbsr = true;
if (readMode) {
_writebsr = _prewrite;
}
_bsr2 = true;
_prewrite = readMode;
_debug('Bank 2 Read/Write');
break;
case LOC.READBSR1: // 0xC088
_readbsr = true;
_writebsr = false;
_bsr2 = false;
_prewrite = false;
_debug('Bank 1 Read');
break;
case LOC.WRITEBSR1: // 0xC089
_readbsr = false;
if (readMode) {
_writebsr = _prewrite;
}
_bsr2 = false;
_prewrite = readMode;
_debug('Bank 1 Write');
break;
case LOC.OFFBSR1: // 0xC08A
_readbsr = false;
_writebsr = false;
_bsr2 = false;
_prewrite = false;
_debug('Bank 1 Off');
break;
case LOC.READWRBSR1: // 0xC08B
_readbsr = true;
if (readMode) {
_writebsr = _prewrite;
}
_bsr2 = false;
_prewrite = readMode;
_debug('Bank 1 Read/Write');
break;
default:
break;
}
_updateBanks();
return result;
}
return {
start: function() {
return 0xd0;
},
end: function() {
return 0xff;
},
ioSwitch: function(off, val) {
return _access(off, val);
},
read: function(page, off) {
var result = 0;
if (page < 0xe0) {
result = _read1.read(page, off);
} else {
result = _read2.read(page, off);
}
return result;
},
write: function(page, off, val) {
if (page < 0xe0) {
_write1.write(page, off, val);
} else {
_write2.write(page, off, val);
}
},
getState: function() {
return {
readbsr: _readbsr,
writebsr: _writebsr,
bsr2: _bsr2,
prewrite: _prewrite,
ram: _ram.getState(),
bank1: _bank1.getState(),
bank2: _bank2.getState()
};
},
setState: function(state) {
_readbsr = state.readbsr;
_writebsr = state.writebsr;
_bsr2 = state.bsr2;
_prewrite = state.prewrite;
_ram.setState(state.ram);
_bank1.setState(state.bank1);
_bank2.setState(state.bank2);
_updateBanks();
}
};
}

194
js/cards/langcard.ts Normal file
View File

@ -0,0 +1,194 @@
/* 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 RAM, { RAMState } from '../ram';
import { debug } from '../util';
import { Card, Memory, byte, Restorable } from '../types';
export interface LanguageCardState {
bank1: RAMState;
bank2: RAMState;
ram: RAMState;
readbsr: boolean;
writebsr: boolean;
bsr2: boolean;
prewrite: boolean;
}
export default class LanguageCard implements Card, Restorable<LanguageCardState> {
private bank1: RAM;
private bank2: RAM;
private ram: RAM;
private readbsr = false;
private writebsr = false;
private bsr2 = false;
private prewrite = false;
private read1: Memory;
private read2: Memory;
private write1: Memory;
private write2: Memory;
constructor(private rom: Memory) {
debug('Language card');
this.bank1 = new RAM(0xd0, 0xdf);
this.bank2 = new RAM(0xd0, 0xdf);
this.ram = new RAM(0xe0, 0xff);
this.write1 = this.rom;
this.write2 = this.rom;
this.read1 = this.rom;
this.read2 = this.rom;
}
private debug(...args: any[]) {
debug.apply(null, args);
}
private updateBanks() {
if (this.readbsr) {
this.read1 = this.bsr2 ? this.bank2 : this.bank1;
this.read2 = this.ram;
} else {
this.read1 = this.rom;
this.read2 = this.rom;
}
if (this.writebsr) {
this.write1 = this.bsr2 ? this.bank2 : this.bank1;
this.write2 = this.ram;
} else {
this.write1 = this.rom;
this.write2 = this.rom;
}
}
// Bank 2
// READBSR2: 0x80
// WRITEBSR2: 0x81
// OFFBSR2: 0x82
// READWRBSR2: 0x83
// Bank 1
// READBSR1: 0x88
// WRITEBSR1: 0x89
// OFFBSR1: 0x8a
// READWRBSR1: 0x8b
private access(off: byte, val?: byte) {
const readMode = val === undefined;
const result = readMode ? 0 : undefined;
const writeSwitch = off & 0x01;
const offSwitch = off & 0x02;
const bank1Switch = off & 0x08;
let bankStr;
let rwStr;
if (writeSwitch) { // $C081, $C083, $C089, $C08B
if (readMode) {
this.writebsr = this.prewrite;
}
this.prewrite = readMode;
if (offSwitch) { // $C083, $C08B
this.readbsr = true;
rwStr = 'Read/Write';
} else { // $C081, $C089
this.readbsr = false;
rwStr = 'Write';
}
} else { // $C080, $C082, $C088, $C08A
this.writebsr = false;
this.prewrite = false;
if (offSwitch) { // $C082, $C08A
this.readbsr = false;
rwStr = 'Off';
} else { // $C080, $C088
this.readbsr = true;
rwStr = 'Read';
}
}
if (bank1Switch) { // C08[8-C]
this.bsr2 = false;
bankStr = 'Bank 1';
} else { // C08[0-3]
this.bsr2 = true;
bankStr = 'Bank 2';
}
this.debug(bankStr, rwStr);
this.updateBanks();
return result;
}
start() {
return 0xd0;
}
end() {
return 0xff;
}
ioSwitch(off: byte, val?: byte) {
return this.access(off, val);
}
read(page: byte, off: byte): byte {
let result: number = 0;
if (page < 0xe0) {
result = this.read1.read(page, off);
} else {
result = this.read2.read(page, off);
}
return result;
}
write(page: byte, off: byte, val: byte) {
if (page < 0xe0) {
this.write1.write(page, off, val);
} else {
this.write2.write(page, off, val);
}
}
getState() {
return {
readbsr: this.readbsr,
writebsr: this.writebsr,
bsr2: this.bsr2,
prewrite: this.prewrite,
ram: this.ram.getState(),
bank1: this.bank1.getState(),
bank2: this.bank2.getState()
};
}
setState(state: LanguageCardState) {
this.readbsr = state.readbsr;
this.writebsr = state.writebsr;
this.bsr2 = state.bsr2;
this.prewrite = state.prewrite;
this.ram.setState(state.ram);
this.bank1.setState(state.bank1);
this.bank2.setState(state.bank2);
this.updateBanks();
}
}

View File

@ -1,445 +0,0 @@
/* 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';
import { rom } from '../roms/cards/smartport';
export default function SmartPort(io, cpu, options ) {
var COMMAND = 0x42;
var UNIT = 0x43;
var ADDRESS_LO = 0x44;
// var ADDRESS_HI = 0x45;
var BLOCK_LO = 0x46;
// var BLOCK_HI = 0x47;
var disks = [];
function _init() {
if (options && options.block) {
rom[0x07] = 0x3C;
debug('DumbPort card');
} else {
debug('SmartPort card');
}
}
function decodeDisk(unit, disk) {
disks[unit] = [];
for (var idx = 0; idx < disk.blocks.length; idx++) {
disks[unit][idx] = new Uint8Array(disk.blocks[idx]);
}
}
function _debug() {
// debug.apply(this, arguments);
}
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;
}
function _access(off, val) {
var result;
var readMode = val === undefined;
switch (off & 0x8f) {
case 0x80:
if (readMode) {
result = 0;
for (var idx = 0; idx < disks.length; idx++) {
result <<= 1;
if (disks[idx]) {
result |= 0x01;
}
}
}
break;
}
return result;
}
_init();
/*
* Interface
*/
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function(page, off) {
var state = cpu.getState();
var cmd;
var unit;
var buffer;
var block;
var blockOff = rom[0xff];
var smartOff = blockOff + 3;
if (off === blockOff && cpu.getSync()) { // Regular block device entry POINT
_debug('block device entry');
cmd = cpu.read(0x00, COMMAND);
unit = cpu.read(0x00, UNIT);
var bufferAddr = new Address(ADDRESS_LO);
var blockAddr = new Address(BLOCK_LO);
var drive = (unit & 0x80) ? 2 : 1;
var driveSlot = (unit & 0x70) >> 4;
buffer = bufferAddr.readAddress();
block = blockAddr.readWord();
_debug('cmd=' + cmd);
_debug('unit=$' + toHex(unit));
_debug('slot=' + driveSlot + ' drive=' + drive);
_debug('buffer=' + toHex(buffer) + ' block=' + toHex(block));
switch (cmd) {
case 0: // INFO
getDeviceInfo(state, drive);
break;
case 1: // READ
readBlock(state, drive, block, buffer);
break;
case 2: // WRITE
writeBlock(state, drive, block, buffer);
break;
case 3: // FORMAT
formatDevice(state, unit);
break;
}
} else if (off == smartOff && cpu.getSync()) {
_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() {
return {
disks: disks.map(
(disk) => disk.map(
(block) => new Uint8Array(block)
)
)
};
},
setState: function(state) {
disks = state.disks.map(
(disk) => disk.map(
(block) => new Uint8Array(block)
)
);
},
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);
}
};
}

464
js/cards/smartport.ts Normal file
View File

@ -0,0 +1,464 @@
/* 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';
import { rom as smartPortRom } from '../roms/cards/smartport';
import { Card, Restorable, byte, word, rom } from '../types';
import CPU6502, { CpuState, flags } from '../cpu6502';
type SmartDisk = Uint8Array[];
interface BlockDevice {
blocks: Uint8Array
}
export interface SmartPortState {
disks: SmartDisk[]
}
export interface SmartPortOptions {
block: boolean;
}
class Address {
lo: byte;
hi: byte;
constructor(private cpu: CPU6502, a: byte | word, b?: byte) {
if (b === undefined) {
this.lo = a & 0xff;
this.hi = a >> 8;
} else {
this.lo = a;
this.hi = b;
}
}
loByte() {
return this.lo;
}
hiByte() {
return this.hi;
}
inc(val: byte) {
return new Address(this.cpu, ((this.hi << 8 | this.lo) + val) & 0xffff);
}
readByte() {
return this.cpu.read(this.hi, this.lo);
}
readWord() {
const readLo = this.readByte();
const readHi = this.inc(1).readByte();
return readHi << 8 | readLo;
}
readAddress() {
const readLo = this.readByte();
const readHi = this.inc(1).readByte();
return new Address(this.cpu, readLo, readHi);
}
writeByte(val: byte) {
this.cpu.write(this.hi, this.lo, val);
}
writeWord(val: word) {
this.writeByte(val & 0xff);
this.inc(1).writeByte(val >> 8);
}
writeAddress(val: Address) {
this.writeByte(val.loByte());
this.inc(1).writeByte(val.hiByte());
}
toString() {
return '$' + toHex(this.hi) + toHex(this.lo);
}
}
// ProDOS zero page locations
const COMMAND = 0x42;
const UNIT = 0x43;
const ADDRESS_LO = 0x44;
// const ADDRESS_HI = 0x45;
const BLOCK_LO = 0x46;
// const BLOCK_HI = 0x47;
export default class SmartPort implements Card, Restorable<SmartPortState> {
private rom: rom;
private disks: SmartDisk[] = [];
constructor(private cpu: CPU6502, options: SmartPortOptions) {
if (options?.block) {
const dumbPortRom = new Uint8Array(smartPortRom);
dumbPortRom[0x07] = 0x3C;
this.rom = dumbPortRom;
debug('DumbPort card');
} else {
debug('SmartPort card');
}
this.rom = smartPortRom;
}
private decodeDisk(unit: number, disk: BlockDevice) {
this.disks[unit] = [];
for (let idx = 0; idx < disk.blocks.length; idx++) {
this.disks[unit][idx] = new Uint8Array(disk.blocks[idx]);
}
}
private debug(..._args: any[]) {
// debug.apply(this, arguments);
}
/*
* dumpBlock
*/
/*
dumpBlock(drive, block) {
const result = '';
const b;
const jdx;
for (const 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
*/
getDeviceInfo(state: CpuState, drive: number) {
if (this.disks[drive]) {
const blocks = this.disks[drive].length;
state.x = blocks & 0xff;
state.y = blocks >> 8;
state.a = 0;
state.s &= ~flags.C;
} else {
state.a = 0x28;
state.s |= flags.C;
}
}
/*
* readBlock
*/
readBlock(state: CpuState, drive: number, block: number, buffer: Address) {
this.debug('read drive=' + drive);
this.debug('read buffer=' + buffer);
this.debug('read block=$' + toHex(block));
if (!this.disks[drive] || !this.disks[drive].length) {
debug('Drive', drive, 'is empty');
return;
}
// debug('read', '\n' + dumpBlock(drive, block));
for (let idx = 0; idx < 512; idx++) {
buffer.writeByte(this.disks[drive][block][idx]);
buffer = buffer.inc(1);
}
state.a = 0;
state.s &= 0xfe;
}
/*
* writeBlock
*/
writeBlock(state: CpuState, drive: number, block: number, buffer: Address) {
this.debug('write drive=' + drive);
this.debug('write buffer=' + buffer);
this.debug('write block=$' + toHex(block));
if (!this.disks[drive] || !this.disks[drive].length) {
debug('Drive', drive, 'is empty');
return;
}
// debug('write', '\n' + dumpBlock(drive, block));
for (let idx = 0; idx < 512; idx++) {
this.disks[drive][block][idx] = buffer.readByte();
buffer = buffer.inc(1);
}
state.a = 0;
state.s &= 0xfe;
}
/*
* formatDevice
*/
formatDevice(state: CpuState, drive: number) {
for (let idx = 0; idx < this.disks[drive].length; idx++) {
this.disks[drive][idx] = new Uint8Array();
for (let jdx = 0; jdx < 512; jdx++) {
this.disks[drive][idx][jdx] = 0;
}
}
state.a = 0;
state.s &= 0xfe;
}
private access(off: byte, val: byte) {
let result;
const readMode = val === undefined;
switch (off & 0x8f) {
case 0x80:
if (readMode) {
result = 0;
for (let idx = 0; idx < this.disks.length; idx++) {
result <<= 1;
if (this.disks[idx]) {
result |= 0x01;
}
}
}
break;
}
return result;
}
/*
* Interface
*/
ioSwitch(off: byte, val: byte) {
return this.access(off, val);
}
read(_page: byte, off: byte) {
const state = this.cpu.getState();
let cmd;
let unit;
let buffer;
let block;
const blockOff = this.rom[0xff];
const smartOff = blockOff + 3;
if (off === blockOff && this.cpu.getSync()) { // Regular block device entry POINT
this.debug('block device entry');
cmd = this.cpu.read(0x00, COMMAND);
unit = this.cpu.read(0x00, UNIT);
const bufferAddr = new Address(this.cpu, ADDRESS_LO);
const blockAddr = new Address(this.cpu, BLOCK_LO);
const drive = (unit & 0x80) ? 2 : 1;
const driveSlot = (unit & 0x70) >> 4;
buffer = bufferAddr.readAddress();
block = blockAddr.readWord();
this.debug('cmd=' + cmd);
this.debug('unit=$' + toHex(unit));
this.debug('slot=' + driveSlot + ' drive=' + drive);
this.debug('buffer=' + buffer + ' block=$' + toHex(block));
switch (cmd) {
case 0: // INFO
this.getDeviceInfo(state, drive);
break;
case 1: // READ
this.readBlock(state, drive, block, buffer);
break;
case 2: // WRITE
this.writeBlock(state, drive, block, buffer);
break;
case 3: // FORMAT
this.formatDevice(state, unit);
break;
}
} else if (off == smartOff && this.cpu.getSync()) {
this.debug('smartport entry');
const stackAddr = new Address(this.cpu, state.sp + 1, 0x01);
let blocks;
const retVal = stackAddr.readAddress();
this.debug('return=' + retVal);
const cmdBlockAddr = retVal.inc(1);
cmd = cmdBlockAddr.readByte();
const cmdListAddr = cmdBlockAddr.inc(1).readAddress();
this.debug('cmd=' + cmd);
this.debug('cmdListAddr=' + cmdListAddr);
stackAddr.writeAddress(retVal.inc(3));
const parameterCount = cmdListAddr.readByte();
unit = cmdListAddr.inc(1).readByte();
buffer = cmdListAddr.inc(2).readAddress();
let status;
this.debug('parameterCount=' + parameterCount);
switch (cmd) {
case 0x00: // INFO
status = cmdListAddr.inc(4).readByte();
this.debug('info unit=' + unit);
this.debug('info buffer=' + buffer);
this.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:
blocks = this.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();
this.readBlock(state, unit, block, buffer);
break;
case 0x02: // WRITE BLOCK
block = cmdListAddr.inc(4).readWord();
this.writeBlock(state, unit, block, buffer);
break;
case 0x03: // FORMAT
this.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;
}
}
this.cpu.setState(state);
return this.rom[off];
}
write() {
}
getState() {
return {
disks: this.disks.map(
(disk) => disk.map(
(block) => new Uint8Array(block)
)
)
};
}
setState(state: SmartPortState) {
this.disks = state.disks.map(
(disk) => disk.map(
(block) => new Uint8Array(block)
)
);
}
setBinary(drive: number, _name: string, fmt: string, data: ArrayBuffer) {
this.disks[drive] = [];
if (fmt == '2mg') {
data = data.slice(64);
}
for (let idx = 0; idx < data.byteLength; idx += 512) {
this.disks[drive].push(new Uint8Array(data.slice(idx, idx + 512)));
}
}
setDisk(drive: number, json: BlockDevice) {
this.decodeDisk(drive, json);
}
}

View File

@ -13,20 +13,54 @@ import { byte, DiskFormat, memory } from '../types';
import { base64_decode, base64_encode } from '../base64'; import { base64_decode, base64_encode } from '../base64';
import { bytify, debug, toHex } from '../util'; import { bytify, debug, toHex } from '../util';
export type Disk = { export interface Disk {
format: DiskFormat, format: DiskFormat
name: string, name: string
volume: byte, volume: byte
tracks: memory[], tracks: memory[]
readOnly: boolean, readOnly: boolean
}; }
export type Drive = { /**
format: DiskFormat, * Base format for JSON defined disks
volume: byte, */
tracks: memory[], export class JSONDiskBase {
readOnly: boolean, type: DiskFormat
dirty: boolean, name: string
volume: byte
readOnly: boolean
}
/**
* JSON Disk format with base64 encoded tracks
*/
export interface Base64JSONDisk extends JSONDiskBase {
encoding: 'base64'
data: string[]
}
/**
* JSON Disk format with byte array tracks
*/
export interface BinaryJSONDisk extends JSONDiskBase {
encoding: 'binary'
data: memory[][]
}
/**
* General JSON Disk format
*/
export type JSONDisk = Base64JSONDisk | BinaryJSONDisk;
export interface Drive {
format: DiskFormat
volume: byte
tracks: memory[]
readOnly: boolean
dirty: boolean
} }
/** /**

View File

@ -76,13 +76,13 @@ var io = apple2.getIO();
var printer = new Printer('#printer-modal .paper'); var printer = new Printer('#printer-modal .paper');
var lc = new LanguageCard(io, rom); var lc = new LanguageCard(rom);
var parallel = new Parallel(io, printer); var parallel = new Parallel(io, printer);
var videoTerm = new VideoTerm(io); var videoTerm = new VideoTerm(io);
var slinky = new RAMFactor(io, 1024 * 1024); var slinky = new RAMFactor(io, 1024 * 1024);
var disk2 = new DiskII(io, driveLights, sectors); var disk2 = new DiskII(io, driveLights, sectors);
var clock = new Thunderclock(io); var clock = new Thunderclock(io);
var smartport = new SmartPort(io, cpu, { block: true }); var smartport = new SmartPort(cpu, { block: true });
initUI(apple2, disk2, smartport, printer, false); initUI(apple2, disk2, smartport, printer, false);

View File

@ -62,7 +62,7 @@ var parallel = new Parallel(io, printer);
var slinky = new RAMFactor(io, 1024 * 1024); var slinky = new RAMFactor(io, 1024 * 1024);
var disk2 = new DiskII(io, driveLights); var disk2 = new DiskII(io, driveLights);
var clock = new Thunderclock(io); var clock = new Thunderclock(io);
var smartport = new SmartPort(io, cpu); var smartport = new SmartPort(cpu);
initUI(apple2, disk2, smartport, printer, options.e); initUI(apple2, disk2, smartport, printer, options.e);

102
js/mmu.ts
View File

@ -695,69 +695,53 @@ export default class MMU implements Memory, Restorable<MMUState> {
_accessLangCard(off: byte, val?: byte) { _accessLangCard(off: byte, val?: byte) {
const readMode = val === undefined; const readMode = val === undefined;
const result = readMode ? 0 : undefined;
switch (off & 0x8b) { const writeSwitch = off & 0x01;
// Language Card Switches const offSwitch = off & 0x02;
const bank1Switch = off & 0x08;
case LOC.READBSR2: // 0xC080 let bankStr;
this._bank1 = false; let rwStr;
this._readbsr = true;
this._writebsr = false; if (writeSwitch) { // 0xC081, 0xC083
this._prewrite = false; if (readMode) {
// _debug('Bank 2 Read'); this._writebsr = this._prewrite;
break;
case LOC.WRITEBSR2: // 0xC081
this._bank1 = false;
this._readbsr = false;
if (readMode) { this._writebsr = this._prewrite; }
this._prewrite = readMode;
// _debug('Bank 2 Write');
break;
case LOC.OFFBSR2: // 0xC082
this._bank1 = false;
this._readbsr = false;
this._writebsr = false;
this._prewrite = false;
// _debug('Bank 2 Off');
break;
case LOC.READWRBSR2: // 0xC083
this._bank1 = false;
this._readbsr = true;
if (readMode) { this._writebsr = this._prewrite; }
this._prewrite = readMode;
// _debug('Bank 2 Read/Write');
break;
case LOC.READBSR1: // 0xC088
this._bank1 = true;
this._readbsr = true;
this._writebsr = false;
this._prewrite = false;
// _debug('Bank 1 Read');
break;
case LOC.WRITEBSR1: // 0xC089
this._bank1 = true;
this._readbsr = false;
if (readMode) { this._writebsr = this._prewrite; }
this._prewrite = readMode;
// _debug('Bank 1 Write');
break;
case LOC.OFFBSR1: // 0xC08A
this._bank1 = true;
this._readbsr = false;
this._writebsr = false;
this._prewrite = false;
// _debug('Bank 1 Off');
break;
case LOC.READWRBSR1: // 0xC08B
this._bank1 = true;
this._readbsr = true;
if (readMode) { this._writebsr = this._prewrite; }
this._prewrite = readMode;
//_debug('Bank 1 Read/Write');
break;
} }
this._prewrite = readMode;
if (offSwitch) { // 0xC08B
this._readbsr = true;
rwStr = 'Read/Write';
} else {
this._readbsr = false;
rwStr = 'Write';
}
} else { // 0xC080, 0xC082
this._writebsr = false;
this._prewrite = false;
if (offSwitch) { // 0xC082
this._readbsr = false;
rwStr = 'Off';
} else { // 0xC080
this._readbsr = true;
rwStr = 'Read';
}
}
if (bank1Switch) {
this._bank1 = true;
bankStr = 'Bank 1';
} else {
this._bank1 = false;
bankStr = 'Bank 2';
}
this._debug(bankStr, rwStr);
this._updateBanks(); this._updateBanks();
}
return result; }
/* /*
* The Big Switch * The Big Switch

View File

@ -47,7 +47,7 @@ export interface MemoryPages extends Memory {
/* An interface card */ /* An interface card */
export interface Card extends Memory, Restorable { export interface Card extends Memory, Restorable {
/* Reset the card */ /* Reset the card */
reset(): void; reset?(): void;
/* Draw card to canvas */ /* Draw card to canvas */
blit?(): ImageData; blit?(): ImageData;