mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Port language card and smartport to TypeScript (#62)
Port language card and smartport to TypeScript.
This commit is contained in:
parent
bf483e1178
commit
a3c7dd136b
@ -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;
|
||||||
|
@ -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
194
js/cards/langcard.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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
464
js/cards/smartport.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
102
js/mmu.ts
@ -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
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user