2021-03-13 21:18:32 +00:00
|
|
|
import { debug, toHex } from '../util';
|
|
|
|
import { rom as smartPortRom } from '../roms/cards/smartport';
|
|
|
|
import { Card, Restorable, byte, word, rom } from '../types';
|
2022-06-19 16:01:44 +00:00
|
|
|
import { MassStorage, BlockDisk, ENCODING_BLOCK, BlockFormat, MassStorageData } from '../formats/types';
|
2021-03-13 21:18:32 +00:00
|
|
|
import CPU6502, { CpuState, flags } from '../cpu6502';
|
2022-06-22 03:34:19 +00:00
|
|
|
import { create2MGFromBlockDisk, HeaderData, read2MGHeader } from '../formats/2mg';
|
2021-07-07 00:04:02 +00:00
|
|
|
import createBlockDisk from '../formats/block';
|
2021-12-22 18:37:21 +00:00
|
|
|
import { ProDOSVolume } from '../formats/prodos';
|
|
|
|
import { dump } from '../formats/prodos/utils';
|
2022-06-05 17:57:04 +00:00
|
|
|
import { DriveNumber } from '../formats/types';
|
2021-03-13 21:18:32 +00:00
|
|
|
|
2021-12-23 04:47:36 +00:00
|
|
|
const ID = 'SMARTPORT.J.S';
|
|
|
|
|
2021-03-13 21:18:32 +00:00
|
|
|
export interface SmartPortState {
|
2022-05-10 15:04:20 +00:00
|
|
|
disks: BlockDisk[];
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface SmartPortOptions {
|
|
|
|
block: boolean;
|
|
|
|
}
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
export interface Callbacks {
|
|
|
|
driveLight: (drive: DriveNumber, on: boolean) => void;
|
|
|
|
dirty: (drive: DriveNumber, dirty: boolean) => void;
|
|
|
|
label: (drive: DriveNumber, name?: string, side?: string) => void;
|
|
|
|
}
|
|
|
|
|
2021-03-13 21:18:32 +00:00
|
|
|
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;
|
|
|
|
|
2021-07-10 18:44:10 +00:00
|
|
|
// const IO_ERROR = 0x27;
|
|
|
|
const NO_DEVICE_CONNECTED = 0x28;
|
|
|
|
const WRITE_PROTECTED = 0x2B;
|
|
|
|
const DEVICE_OFFLINE = 0x2F;
|
|
|
|
// const VOLUME_DIRECTORY_NOT_FOUND = 0x45;
|
|
|
|
// const NOT_A_PRODOS_DISK = 0x52;
|
|
|
|
// const VOLUME_CONTROL_BLOCK_FULL = 0x55;
|
|
|
|
// const BAD_BUFFER_ADDRESS = 0x56;
|
|
|
|
// const DUPLICATE_VOLUME_ONLINE = 0x57;
|
|
|
|
|
2021-12-23 04:47:36 +00:00
|
|
|
// Type: Device
|
|
|
|
// $00: Memory Expansion Card (RAM disk)
|
|
|
|
// $01: 3.5" disk
|
|
|
|
// $02: ProFile-type hard disk
|
|
|
|
// $03: Generic SCSI
|
|
|
|
// $04: ROM disk
|
|
|
|
// $05: SCSI CD-ROM
|
|
|
|
// $06: SCSI tape or other SCSI sequential device
|
|
|
|
// $07: SCSI hard disk
|
|
|
|
const DEVICE_TYPE_SCSI_HD = 0x07;
|
|
|
|
// $08: Reserved
|
|
|
|
// $09: SCSI printer
|
|
|
|
// $0A: 5-1/4" disk
|
|
|
|
// $0B: Reserved
|
|
|
|
// $0C: Reserved
|
|
|
|
// $0D: Printer
|
|
|
|
// $0E: Clock
|
|
|
|
// $0F: Modem
|
2022-06-05 17:57:04 +00:00
|
|
|
export default class SmartPort implements Card, MassStorage<BlockFormat>, Restorable<SmartPortState> {
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
private rom: rom;
|
2021-07-07 00:04:02 +00:00
|
|
|
private disks: BlockDisk[] = [];
|
2022-06-05 17:57:04 +00:00
|
|
|
private busy: boolean[] = [];
|
|
|
|
private busyTimeout: ReturnType<typeof setTimeout>[] = [];
|
2022-06-19 16:01:44 +00:00
|
|
|
private ext: string[] = [];
|
2022-06-22 03:34:19 +00:00
|
|
|
private metadata: Array<HeaderData|null> = [];
|
2022-06-05 17:57:04 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
private cpu: CPU6502,
|
|
|
|
private callbacks: Callbacks | null,
|
|
|
|
options: SmartPortOptions
|
|
|
|
) {
|
2021-03-13 21:18:32 +00:00
|
|
|
if (options?.block) {
|
2022-05-18 15:19:45 +00:00
|
|
|
const dumbPortRom = new Uint8Array(smartPortRom);
|
2021-03-13 21:18:32 +00:00
|
|
|
dumbPortRom[0x07] = 0x3C;
|
|
|
|
this.rom = dumbPortRom;
|
|
|
|
debug('DumbPort card');
|
|
|
|
} else {
|
|
|
|
debug('SmartPort card');
|
2021-03-14 00:08:24 +00:00
|
|
|
this.rom = smartPortRom;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-18 02:08:28 +00:00
|
|
|
private debug(..._args: unknown[]) {
|
2021-03-13 21:18:32 +00:00
|
|
|
// debug.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
private driveLight(drive: DriveNumber) {
|
|
|
|
if (!this.busy[drive]) {
|
|
|
|
this.busy[drive] = true;
|
|
|
|
this.callbacks?.driveLight(drive, true);
|
|
|
|
}
|
|
|
|
clearTimeout(this.busyTimeout[drive]);
|
|
|
|
this.busyTimeout[drive] = setTimeout(() => {
|
|
|
|
this.busy[drive] = false;
|
|
|
|
this.callbacks?.driveLight(drive, false);
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
|
2021-03-13 21:18:32 +00:00
|
|
|
/*
|
|
|
|
* dumpBlock
|
|
|
|
*/
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
dumpBlock(drive: DriveNumber, block: number) {
|
2021-07-07 00:04:02 +00:00
|
|
|
let result = '';
|
|
|
|
let b;
|
|
|
|
let jdx;
|
|
|
|
|
|
|
|
for (let idx = 0; idx < 32; idx++) {
|
2021-03-13 21:18:32 +00:00
|
|
|
result += toHex(idx << 4, 4) + ': ';
|
|
|
|
for (jdx = 0; jdx < 16; jdx++) {
|
2021-07-07 00:04:02 +00:00
|
|
|
b = this.disks[drive].blocks[block][idx * 16 + jdx];
|
2022-05-18 15:19:45 +00:00
|
|
|
if (jdx === 8) {
|
2021-03-13 21:18:32 +00:00
|
|
|
result += ' ';
|
|
|
|
}
|
|
|
|
result += toHex(b) + ' ';
|
|
|
|
}
|
|
|
|
result += ' ';
|
|
|
|
for (jdx = 0; jdx < 16; jdx++) {
|
2021-07-07 00:04:02 +00:00
|
|
|
b = this.disks[drive].blocks[block][idx * 16 + jdx] & 0x7f;
|
2022-05-18 15:19:45 +00:00
|
|
|
if (jdx === 8) {
|
2021-03-13 21:18:32 +00:00
|
|
|
result += ' ';
|
|
|
|
}
|
|
|
|
if (b >= 0x20 && b < 0x7f) {
|
|
|
|
result += String.fromCharCode(b);
|
|
|
|
} else {
|
|
|
|
result += '.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result += '\n';
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2021-07-07 00:04:02 +00:00
|
|
|
|
2021-03-13 21:18:32 +00:00
|
|
|
/*
|
|
|
|
* getDeviceInfo
|
|
|
|
*/
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
getDeviceInfo(state: CpuState, drive: DriveNumber) {
|
2021-03-13 21:18:32 +00:00
|
|
|
if (this.disks[drive]) {
|
2021-07-07 00:04:02 +00:00
|
|
|
const blocks = this.disks[drive].blocks.length;
|
2021-03-13 21:18:32 +00:00
|
|
|
state.x = blocks & 0xff;
|
|
|
|
state.y = blocks >> 8;
|
|
|
|
|
|
|
|
state.a = 0;
|
|
|
|
state.s &= ~flags.C;
|
|
|
|
} else {
|
2021-07-10 18:44:10 +00:00
|
|
|
state.a = NO_DEVICE_CONNECTED;
|
2021-03-13 21:18:32 +00:00
|
|
|
state.s |= flags.C;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* readBlock
|
|
|
|
*/
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
readBlock(state: CpuState, drive: DriveNumber, block: number, buffer: Address) {
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`read drive=${drive}`);
|
|
|
|
this.debug(`read buffer=${buffer.toString()}`);
|
|
|
|
this.debug(`read block=$${toHex(block)}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
2021-07-07 00:04:02 +00:00
|
|
|
if (!this.disks[drive]?.blocks.length) {
|
2021-03-13 21:18:32 +00:00
|
|
|
debug('Drive', drive, 'is empty');
|
2021-07-10 18:44:10 +00:00
|
|
|
state.a = DEVICE_OFFLINE;
|
|
|
|
state.s |= flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// debug('read', '\n' + dumpBlock(drive, block));
|
2022-06-05 17:57:04 +00:00
|
|
|
this.driveLight(drive);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
for (let idx = 0; idx < 512; idx++) {
|
2021-07-07 00:04:02 +00:00
|
|
|
buffer.writeByte(this.disks[drive].blocks[block][idx]);
|
2021-03-13 21:18:32 +00:00
|
|
|
buffer = buffer.inc(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.a = 0;
|
2021-07-10 18:44:10 +00:00
|
|
|
state.s &= ~flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* writeBlock
|
|
|
|
*/
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
writeBlock(state: CpuState, drive: DriveNumber, block: number, buffer: Address) {
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`write drive=${drive}`);
|
|
|
|
this.debug(`write buffer=${buffer.toString()}`);
|
|
|
|
this.debug(`write block=$${toHex(block)}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
2021-07-07 00:04:02 +00:00
|
|
|
if (!this.disks[drive]?.blocks.length) {
|
2021-03-13 21:18:32 +00:00
|
|
|
debug('Drive', drive, 'is empty');
|
2021-07-10 18:44:10 +00:00
|
|
|
state.a = DEVICE_OFFLINE;
|
|
|
|
state.s |= flags.C;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.disks[drive].readOnly) {
|
|
|
|
debug('Drive', drive, 'is write protected');
|
|
|
|
state.a = WRITE_PROTECTED;
|
|
|
|
state.s |= flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// debug('write', '\n' + dumpBlock(drive, block));
|
2022-06-05 17:57:04 +00:00
|
|
|
this.driveLight(drive);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
for (let idx = 0; idx < 512; idx++) {
|
2021-07-07 00:04:02 +00:00
|
|
|
this.disks[drive].blocks[block][idx] = buffer.readByte();
|
2021-03-13 21:18:32 +00:00
|
|
|
buffer = buffer.inc(1);
|
|
|
|
}
|
|
|
|
state.a = 0;
|
2022-06-13 02:39:03 +00:00
|
|
|
state.s &= ~flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* formatDevice
|
|
|
|
*/
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
formatDevice(state: CpuState, drive: DriveNumber) {
|
2021-07-10 18:44:10 +00:00
|
|
|
if (!this.disks[drive]?.blocks.length) {
|
|
|
|
debug('Drive', drive, 'is empty');
|
|
|
|
state.a = DEVICE_OFFLINE;
|
|
|
|
state.s |= flags.C;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.disks[drive].readOnly) {
|
|
|
|
debug('Drive', drive, 'is write protected');
|
|
|
|
state.a = WRITE_PROTECTED;
|
|
|
|
state.s |= flags.C;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-07 00:04:02 +00:00
|
|
|
for (let idx = 0; idx < this.disks[drive].blocks.length; idx++) {
|
|
|
|
this.disks[drive].blocks[idx] = new Uint8Array();
|
2021-03-13 21:18:32 +00:00
|
|
|
for (let jdx = 0; jdx < 512; jdx++) {
|
2021-07-07 00:04:02 +00:00
|
|
|
this.disks[drive].blocks[idx][jdx] = 0;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.a = 0;
|
2021-07-10 18:44:10 +00:00
|
|
|
state.s &= flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`cmd=${cmd}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
this.debug('unit=$' + toHex(unit));
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`slot=${driveSlot} drive=${drive}`);
|
|
|
|
this.debug(`buffer=${buffer.toString()} block=$${toHex(block)}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
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
|
2022-06-05 17:57:04 +00:00
|
|
|
this.formatDevice(state, drive);
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-05-18 15:19:45 +00:00
|
|
|
} else if (off === smartOff && this.cpu.getSync()) {
|
2021-03-13 21:18:32 +00:00
|
|
|
this.debug('smartport entry');
|
|
|
|
const stackAddr = new Address(this.cpu, state.sp + 1, 0x01);
|
|
|
|
let blocks;
|
|
|
|
|
|
|
|
const retVal = stackAddr.readAddress();
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`return=${retVal.toString()}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
const cmdBlockAddr = retVal.inc(1);
|
|
|
|
cmd = cmdBlockAddr.readByte();
|
|
|
|
const cmdListAddr = cmdBlockAddr.inc(1).readAddress();
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`cmd=${cmd}`);
|
|
|
|
this.debug(`cmdListAddr=${cmdListAddr.toString()}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
|
|
|
|
stackAddr.writeAddress(retVal.inc(3));
|
|
|
|
|
|
|
|
const parameterCount = cmdListAddr.readByte();
|
|
|
|
unit = cmdListAddr.inc(1).readByte();
|
2022-06-05 17:57:04 +00:00
|
|
|
const drive = unit ? 2 : 1;
|
2021-03-13 21:18:32 +00:00
|
|
|
buffer = cmdListAddr.inc(2).readAddress();
|
|
|
|
let status;
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`parameterCount=${parameterCount}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
switch (cmd) {
|
|
|
|
case 0x00: // INFO
|
|
|
|
status = cmdListAddr.inc(4).readByte();
|
2022-05-31 15:38:40 +00:00
|
|
|
this.debug(`info unit=${unit}`);
|
|
|
|
this.debug(`info buffer=${buffer.toString()}`);
|
|
|
|
this.debug(`info status=${status}`);
|
2021-03-13 21:18:32 +00:00
|
|
|
switch (unit) {
|
|
|
|
case 0:
|
|
|
|
switch (status) {
|
|
|
|
case 0:
|
2021-07-10 18:44:10 +00:00
|
|
|
buffer.writeByte(2); // two devices
|
2021-03-13 21:18:32 +00:00
|
|
|
buffer.inc(1).writeByte(1 << 6); // no interrupts
|
2021-12-23 04:47:36 +00:00
|
|
|
buffer.inc(2).writeByte(0x2); // Other vendor
|
|
|
|
buffer.inc(3).writeByte(0x0); // Other vendor
|
2021-03-13 21:18:32 +00:00
|
|
|
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;
|
2021-07-10 18:44:10 +00:00
|
|
|
state.s &= ~flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: // Unit 1
|
|
|
|
switch (status) {
|
|
|
|
case 0:
|
2021-12-23 04:47:36 +00:00
|
|
|
blocks = this.disks[unit]?.blocks.length ?? 0;
|
2021-03-13 21:18:32 +00:00
|
|
|
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;
|
2021-07-10 18:44:10 +00:00
|
|
|
state.s &= ~flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
2021-12-23 04:47:36 +00:00
|
|
|
case 3:
|
|
|
|
blocks = this.disks[unit]?.blocks.length ?? 0;
|
|
|
|
buffer.writeByte(0xf0); // W/R Block device in drive
|
|
|
|
buffer.inc(1).writeByte(blocks & 0xff); // Blocks low byte
|
|
|
|
buffer.inc(2).writeByte((blocks & 0xff00) >> 8); // Blocks middle byte
|
|
|
|
buffer.inc(3).writeByte((blocks & 0xff0000) >> 16); // Blocks high byte
|
|
|
|
buffer.inc(4).writeByte(ID.length); // Vendor ID length
|
|
|
|
for (let idx = 0; idx < ID.length; idx++) { // Vendor ID
|
|
|
|
buffer.inc(5 + idx).writeByte(ID.charCodeAt(idx));
|
|
|
|
}
|
|
|
|
buffer.inc(21).writeByte(DEVICE_TYPE_SCSI_HD); // Device Type
|
|
|
|
buffer.inc(22).writeByte(0x0); // Device Subtype
|
|
|
|
buffer.inc(23).writeWord(0x0101); // Version
|
|
|
|
state.x = 24;
|
|
|
|
state.y = 0;
|
|
|
|
state.a = 0;
|
|
|
|
state.s &= ~flags.C;
|
|
|
|
break;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
state.a = 0;
|
2021-07-10 18:44:10 +00:00
|
|
|
state.s &= ~flags.C;
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x01: // READ BLOCK
|
|
|
|
block = cmdListAddr.inc(4).readWord();
|
2022-06-05 17:57:04 +00:00
|
|
|
this.readBlock(state, drive, block, buffer);
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x02: // WRITE BLOCK
|
|
|
|
block = cmdListAddr.inc(4).readWord();
|
2022-06-05 17:57:04 +00:00
|
|
|
this.writeBlock(state, drive, block, buffer);
|
2021-03-13 21:18:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03: // FORMAT
|
2022-06-05 17:57:04 +00:00
|
|
|
this.formatDevice(state, drive);
|
2021-03-13 21:18:32 +00:00
|
|
|
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() {
|
2022-05-31 15:38:40 +00:00
|
|
|
// not writable
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getState() {
|
|
|
|
return {
|
|
|
|
disks: this.disks.map(
|
2021-07-07 00:04:02 +00:00
|
|
|
(disk) => {
|
|
|
|
const result: BlockDisk = {
|
|
|
|
blocks: disk.blocks.map(
|
|
|
|
(block) => new Uint8Array(block)
|
|
|
|
),
|
|
|
|
encoding: ENCODING_BLOCK,
|
|
|
|
readOnly: disk.readOnly,
|
|
|
|
name: disk.name,
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
2021-03-13 21:18:32 +00:00
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
setState(state: SmartPortState) {
|
|
|
|
this.disks = state.disks.map(
|
2021-07-07 00:04:02 +00:00
|
|
|
(disk) => {
|
|
|
|
const result: BlockDisk = {
|
|
|
|
blocks: disk.blocks.map(
|
|
|
|
(block) => new Uint8Array(block)
|
|
|
|
),
|
|
|
|
encoding: ENCODING_BLOCK,
|
|
|
|
readOnly: disk.readOnly,
|
|
|
|
name: disk.name,
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
2021-03-13 21:18:32 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-06-05 17:57:04 +00:00
|
|
|
setBinary(drive: DriveNumber, name: string, fmt: string, rawData: ArrayBuffer) {
|
2021-07-07 00:04:02 +00:00
|
|
|
const volume = 254;
|
|
|
|
const readOnly = false;
|
2022-05-18 15:19:45 +00:00
|
|
|
if (fmt === '2mg') {
|
2022-06-22 03:34:19 +00:00
|
|
|
const header = read2MGHeader(rawData);
|
|
|
|
this.metadata[drive] = header;
|
|
|
|
const { bytes, offset } = header;
|
2021-07-07 00:04:02 +00:00
|
|
|
rawData = rawData.slice(offset, offset + bytes);
|
2022-06-22 03:34:19 +00:00
|
|
|
} else {
|
|
|
|
this.metadata[drive] = null;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
2021-07-07 00:04:02 +00:00
|
|
|
const options = {
|
|
|
|
rawData,
|
|
|
|
name,
|
|
|
|
readOnly,
|
|
|
|
volume,
|
|
|
|
};
|
2021-03-13 21:18:32 +00:00
|
|
|
|
2022-06-19 16:01:44 +00:00
|
|
|
this.ext[drive] = fmt;
|
2021-07-07 00:04:02 +00:00
|
|
|
this.disks[drive] = createBlockDisk(options);
|
2022-06-05 17:57:04 +00:00
|
|
|
this.callbacks?.label(drive, name);
|
2021-07-07 00:04:02 +00:00
|
|
|
|
2021-12-22 18:37:21 +00:00
|
|
|
const prodos = new ProDOSVolume(this.disks[drive]);
|
|
|
|
dump(prodos);
|
|
|
|
|
2021-07-07 00:04:02 +00:00
|
|
|
return true;
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|
2022-06-19 16:01:44 +00:00
|
|
|
|
|
|
|
getBinary(drive: number): MassStorageData | null {
|
|
|
|
if (!this.disks[drive]) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-06-22 03:34:19 +00:00
|
|
|
const disk = this.disks[drive];
|
|
|
|
const ext = this.ext[drive];
|
|
|
|
const { name } = disk;
|
|
|
|
let data: ArrayBuffer;
|
|
|
|
if (ext === '2mg') {
|
|
|
|
data = create2MGFromBlockDisk(this.metadata[drive], disk);
|
|
|
|
} else {
|
|
|
|
const { blocks } = disk;
|
|
|
|
const byteArray = new Uint8Array(blocks.length * 512);
|
|
|
|
for (let idx = 0; idx < blocks.length; idx++) {
|
|
|
|
byteArray.set(blocks[idx], idx * 512);
|
|
|
|
}
|
|
|
|
data = byteArray.buffer;
|
2022-06-19 16:01:44 +00:00
|
|
|
}
|
|
|
|
return {
|
2022-06-22 03:34:19 +00:00
|
|
|
name,
|
|
|
|
ext,
|
|
|
|
data,
|
2022-06-19 16:01:44 +00:00
|
|
|
};
|
|
|
|
}
|
2021-03-13 21:18:32 +00:00
|
|
|
}
|