mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Refactor disk parsing into webworker
This commit is contained in:
parent
3abd168627
commit
2947891eae
418
js/cards/cffa.js
418
js/cards/cffa.js
|
@ -1,418 +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/cffa';
|
||||
import _2MG from '../formats/2mg';
|
||||
import { ProDOSVolume } from '../formats/prodos';
|
||||
import BlockVolume from '../formats/block';
|
||||
import { dump } from '../formats/prodos/utils';
|
||||
|
||||
export default function CFFA() {
|
||||
var COMMANDS = {
|
||||
ATACRead: 0x20,
|
||||
ATACWrite: 0x30,
|
||||
ATAIdentify: 0xEC
|
||||
};
|
||||
|
||||
// CFFA Card Settings
|
||||
|
||||
var SETTINGS = {
|
||||
Max32MBPartitionsDev0: 0x800,
|
||||
Max32MBPartitionsDev1: 0x801,
|
||||
DefaultBootDevice: 0x802,
|
||||
DefaultBootPartition: 0x803,
|
||||
Reserved: 0x804, // 4 bytes
|
||||
WriteProtectBits: 0x808,
|
||||
MenuSnagMask: 0x809,
|
||||
MenuSnagKey: 0x80A,
|
||||
BootTimeDelayTenths: 0x80B,
|
||||
BusResetSeconds: 0x80C,
|
||||
CheckDeviceTenths: 0x80D,
|
||||
ConfigOptionBits: 0x80E,
|
||||
BlockOffsetDev0: 0x80F, // 3 bytes
|
||||
BlockOffsetDev1: 0x812, // 3 bytes
|
||||
Unused: 0x815
|
||||
};
|
||||
|
||||
// CFFA ATA Register Locations
|
||||
|
||||
var LOC = {
|
||||
ATADataHigh: 0x80,
|
||||
SetCSMask: 0x81,
|
||||
ClearCSMask: 0x82,
|
||||
WriteEEPROM: 0x83,
|
||||
NoWriteEEPROM: 0x84,
|
||||
ATADevCtrl: 0x86,
|
||||
ATAAltStatus: 0x86,
|
||||
ATADataLow: 0x88,
|
||||
AError: 0x89,
|
||||
ASectorCnt: 0x8a,
|
||||
ASector: 0x8b,
|
||||
ATACylinder: 0x8c,
|
||||
ATACylinderH: 0x8d,
|
||||
ATAHead: 0x8e,
|
||||
ATACommand: 0x8f,
|
||||
ATAStatus: 0x8f
|
||||
};
|
||||
|
||||
// ATA Status Bits
|
||||
|
||||
var STATUS = {
|
||||
BSY: 0x80, // Busy
|
||||
DRDY: 0x40, // Drive ready. 1 when ready
|
||||
DWF: 0x20, // Drive write fault. 1 when fault
|
||||
DSC: 0x10, // Disk seek complete. 1 when not seeking
|
||||
DRQ: 0x08, // Data request. 1 when ready to write
|
||||
CORR: 0x04, // Correct data. 1 on correctable error
|
||||
IDX: 0x02, // 1 once per revolution
|
||||
ERR: 0x01 // Error. 1 on error
|
||||
};
|
||||
|
||||
// ATA Identity Block Locations
|
||||
|
||||
var IDENTITY = {
|
||||
SectorCountLow: 58,
|
||||
SectorCountHigh: 57
|
||||
};
|
||||
|
||||
// CFFA internal Flags
|
||||
|
||||
var _disableSignalling = false;
|
||||
var _writeEEPROM = true;
|
||||
|
||||
var _lba = true;
|
||||
|
||||
// LBA/CHS registers
|
||||
|
||||
var _sectorCnt = 1;
|
||||
var _sector = 0;
|
||||
var _cylinder = 0;
|
||||
var _cylinderH = 0;
|
||||
var _head = 0;
|
||||
var _drive = 0;
|
||||
|
||||
// CFFA Data High register
|
||||
|
||||
var _dataHigh = 0;
|
||||
|
||||
// Current Sector
|
||||
|
||||
var _curSector = [];
|
||||
var _curWord = 0;
|
||||
|
||||
// ATA Status registers
|
||||
|
||||
var _interruptsEnabled = false;
|
||||
var _altStatus = 0;
|
||||
var _error = 0;
|
||||
|
||||
var _identity = [[], []];
|
||||
|
||||
// Disk data
|
||||
|
||||
var _partitions = [
|
||||
// Drive 1
|
||||
[],
|
||||
// Drive 2
|
||||
[]
|
||||
];
|
||||
|
||||
var _sectors = [
|
||||
// Drive 1
|
||||
[],
|
||||
// Drive 2
|
||||
[]
|
||||
];
|
||||
|
||||
function _init() {
|
||||
debug('CFFA');
|
||||
|
||||
for (var idx = 0; idx < 0x100; idx++) {
|
||||
_identity[0][idx] = 0;
|
||||
_identity[1][idx] = 0;
|
||||
}
|
||||
|
||||
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
|
||||
rom[SETTINGS.Max32MBPartitionsDev1] = 0x0;
|
||||
rom[SETTINGS.BootTimeDelayTenths] = 0x5; // 0.5 seconds
|
||||
rom[SETTINGS.CheckDeviceTenths] = 0x5; // 0.5 seconds
|
||||
}
|
||||
|
||||
// Verbose debug method
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _reset() {
|
||||
_debug('reset');
|
||||
|
||||
_sectorCnt = 1;
|
||||
_sector = 0;
|
||||
_cylinder = 0;
|
||||
_cylinderH = 0;
|
||||
_head = 0;
|
||||
_drive = 0;
|
||||
|
||||
_dataHigh = 0;
|
||||
}
|
||||
|
||||
// Convert status register into readable string
|
||||
|
||||
function _statusString(status) {
|
||||
var statusArray = [];
|
||||
for (var flag in STATUS) {
|
||||
if(status & STATUS[flag]) {
|
||||
statusArray.push(flag);
|
||||
}
|
||||
}
|
||||
return statusArray.join('|');
|
||||
}
|
||||
|
||||
// Dump sector as hex and ascii
|
||||
|
||||
function _dumpSector(sector) {
|
||||
if (sector >= _sectors[_drive].length) {
|
||||
_debug('dump sector out of range', sector);
|
||||
return;
|
||||
}
|
||||
for (var idx = 0; idx < 16; idx++) {
|
||||
var row = [];
|
||||
var charRow = [];
|
||||
for (var jdx = 0; jdx < 16; jdx++) {
|
||||
var val = _sectors[_drive][sector][idx * 16 + jdx];
|
||||
row.push(toHex(val, 4));
|
||||
var low = val & 0x7f;
|
||||
var hi = val >> 8 & 0x7f;
|
||||
charRow.push(low > 0x1f ? String.fromCharCode(low) : '.');
|
||||
charRow.push(hi > 0x1f ? String.fromCharCode(hi) : '.');
|
||||
}
|
||||
_debug(row.join(' '), ' ', charRow.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
// Card I/O access
|
||||
|
||||
function _access(off, val) {
|
||||
var readMode = val === undefined;
|
||||
var retVal = readMode ? 0 : undefined;
|
||||
var sector;
|
||||
|
||||
if (readMode) {
|
||||
switch (off & 0x8f) {
|
||||
case LOC.ATADataHigh: // 0x00
|
||||
retVal = _dataHigh;
|
||||
break;
|
||||
case LOC.SetCSMask: // 0x01
|
||||
_disableSignalling = true;
|
||||
break;
|
||||
case LOC.ClearCSMask: // 0x02
|
||||
_disableSignalling = false;
|
||||
break;
|
||||
case LOC.WriteEEPROM: // 0x03
|
||||
_writeEEPROM = true;
|
||||
break;
|
||||
case LOC.NoWriteEEPROM: // 0x04
|
||||
_writeEEPROM = false;
|
||||
break;
|
||||
case LOC.ATAAltStatus: // 0x06
|
||||
retVal = _altStatus;
|
||||
break;
|
||||
case LOC.ATADataLow: // 0x08
|
||||
_dataHigh = _curSector[_curWord] >> 8;
|
||||
retVal = _curSector[_curWord] & 0xff;
|
||||
if (!_disableSignalling) {
|
||||
_curWord++;
|
||||
}
|
||||
break;
|
||||
case LOC.AError: // 0x09
|
||||
retVal = _error;
|
||||
break;
|
||||
case LOC.ASectorCnt: // 0x0A
|
||||
retVal = _sectorCnt;
|
||||
break;
|
||||
case LOC.ASector: // 0x0B
|
||||
retVal = _sector;
|
||||
break;
|
||||
case LOC.ATACylinder: // 0x0C
|
||||
retVal = _cylinder;
|
||||
break;
|
||||
case LOC.ATACylinderH: // 0x0D
|
||||
retVal = _cylinderH;
|
||||
break;
|
||||
case LOC.ATAHead: // 0x0E
|
||||
retVal = _head | (_lba ? 0x40 : 0) | (_drive ? 0x10 : 0) | 0xA0;
|
||||
break;
|
||||
case LOC.ATAStatus: // 0x0F
|
||||
retVal = _sectors[_drive].length > 0 ? STATUS.DRDY | STATUS.DSC : 0;
|
||||
_debug('returning status', _statusString(retVal));
|
||||
break;
|
||||
default:
|
||||
debug('read unknown soft switch', toHex(off));
|
||||
}
|
||||
|
||||
if (off & 0x7) { // Anything but data high/low
|
||||
_debug('read soft switch', toHex(off), toHex(retVal));
|
||||
}
|
||||
} else {
|
||||
if (off & 0x7) { // Anything but data high/low
|
||||
_debug('write soft switch', toHex(off), toHex(val));
|
||||
}
|
||||
|
||||
switch (off & 0x8f) {
|
||||
case LOC.ATADataHigh: // 0x00
|
||||
_dataHigh = val;
|
||||
break;
|
||||
case LOC.SetCSMask: // 0x01
|
||||
_disableSignalling = true;
|
||||
break;
|
||||
case LOC.ClearCSMask: // 0x02
|
||||
_disableSignalling = false;
|
||||
break;
|
||||
case LOC.WriteEEPROM: // 0x03
|
||||
_writeEEPROM = true;
|
||||
break;
|
||||
case LOC.NoWriteEEPROM: // 0x04
|
||||
_writeEEPROM = false;
|
||||
break;
|
||||
case LOC.ATADevCtrl: // 0x06
|
||||
_debug('devCtrl:', toHex(val));
|
||||
_interruptsEnabled = (val & 0x04) ? true : false;
|
||||
_debug('Interrupts', _interruptsEnabled ? 'enabled' : 'disabled');
|
||||
if (val & 0x02) {
|
||||
_reset();
|
||||
}
|
||||
break;
|
||||
case LOC.ATADataLow: // 0x08
|
||||
_curSector[_curWord] = _dataHigh << 8 | val;
|
||||
_curWord++;
|
||||
break;
|
||||
case LOC.ASectorCnt: // 0x0a
|
||||
_debug('setting sector count', val);
|
||||
_sectorCnt = val;
|
||||
break;
|
||||
case LOC.ASector: // 0x0b
|
||||
_debug('setting sector', toHex(val));
|
||||
_sector = val;
|
||||
break;
|
||||
case LOC.ATACylinder: // 0x0c
|
||||
_debug('setting cylinder', toHex(val));
|
||||
_cylinder = val;
|
||||
break;
|
||||
case LOC.ATACylinderH: // 0x0d
|
||||
_debug('setting cylinder high', toHex(val));
|
||||
_cylinderH = val;
|
||||
break;
|
||||
case LOC.ATAHead:
|
||||
_head = val & 0xf;
|
||||
_lba = val & 0x40 ? true : false;
|
||||
_drive = val & 0x10 ? 1 : 0;
|
||||
_debug('setting head', toHex(val & 0xf), 'drive', _drive);
|
||||
if (!_lba) {
|
||||
console.error('CHS mode not supported');
|
||||
}
|
||||
break;
|
||||
case LOC.ATACommand: // 0x0f
|
||||
_debug('command:', toHex(val));
|
||||
sector = _head << 24 | _cylinderH << 16 | _cylinder << 8 | _sector;
|
||||
_dumpSector(sector);
|
||||
|
||||
switch (val) {
|
||||
case COMMANDS.ATAIdentify:
|
||||
_debug('ATA identify');
|
||||
_curSector = _identity[_drive];
|
||||
_curWord = 0;
|
||||
break;
|
||||
case COMMANDS.ATACRead:
|
||||
_debug('ATA read sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
|
||||
_curSector = _sectors[_drive][sector];
|
||||
_curWord = 0;
|
||||
break;
|
||||
case COMMANDS.ATACWrite:
|
||||
_debug('ATA write sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
|
||||
_curSector = _sectors[_drive][sector];
|
||||
_curWord = 0;
|
||||
break;
|
||||
default:
|
||||
debug('unknown command', toHex(val));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug('write unknown soft switch', toHex(off), toHex(val));
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
ioSwitch: function (off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
|
||||
read: function(page, off) {
|
||||
return rom[(page - 0xc0) << 8 | off];
|
||||
},
|
||||
|
||||
write: function(page, off, val) {
|
||||
if (_writeEEPROM) {
|
||||
_debug('writing', toHex(page << 8 | off), toHex(val));
|
||||
rom[(page - 0xc0) << 8 | off] - val;
|
||||
}
|
||||
},
|
||||
|
||||
getState() {
|
||||
// TODO CFFA State
|
||||
return {};
|
||||
},
|
||||
|
||||
setState(_) {},
|
||||
|
||||
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
|
||||
|
||||
setBinary: function(drive, name, ext, rawData) {
|
||||
drive = drive - 1;
|
||||
var disk;
|
||||
var options = {
|
||||
rawData,
|
||||
name
|
||||
};
|
||||
|
||||
if (ext === '2mg') {
|
||||
disk = new _2MG(options);
|
||||
} else {
|
||||
disk = new BlockVolume(options);
|
||||
}
|
||||
|
||||
// Convert 512 byte blocks into 256 word sectors
|
||||
_sectors[drive] = disk.blocks.map(function(block) {
|
||||
return new Uint16Array(block.buffer);
|
||||
});
|
||||
|
||||
_identity[drive][IDENTITY.SectorCountHigh] = _sectors[0].length & 0xffff;
|
||||
_identity[drive][IDENTITY.SectorCountLow] = _sectors[0].length >> 16;
|
||||
|
||||
var prodos = new ProDOSVolume(disk);
|
||||
dump(prodos);
|
||||
|
||||
_partitions[drive] = prodos;
|
||||
|
||||
if (drive) {
|
||||
rom[SETTINGS.Max32MBPartitionsDev1] = 0x1;
|
||||
} else {
|
||||
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
425
js/cards/cffa.ts
Normal file
425
js/cards/cffa.ts
Normal file
|
@ -0,0 +1,425 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import type { byte, Card } from '../types';
|
||||
import { debug, toHex } from '../util';
|
||||
import { rom as readOnlyRom } from '../roms/cards/cffa';
|
||||
import { read2MGHeader } from '../formats/2mg';
|
||||
import { ProDOSVolume } from '../formats/prodos';
|
||||
import BlockVolume from '../formats/block';
|
||||
import { dump } from '../formats/prodos/utils';
|
||||
import { DiskFormat, MassStorage } from 'js/formats/types';
|
||||
|
||||
const rom = new Uint8Array(readOnlyRom);
|
||||
|
||||
const COMMANDS = {
|
||||
ATACRead: 0x20,
|
||||
ATACWrite: 0x30,
|
||||
ATAIdentify: 0xEC
|
||||
};
|
||||
|
||||
// CFFA Card Settings
|
||||
|
||||
const SETTINGS = {
|
||||
Max32MBPartitionsDev0: 0x800,
|
||||
Max32MBPartitionsDev1: 0x801,
|
||||
DefaultBootDevice: 0x802,
|
||||
DefaultBootPartition: 0x803,
|
||||
Reserved: 0x804, // 4 bytes
|
||||
WriteProtectBits: 0x808,
|
||||
MenuSnagMask: 0x809,
|
||||
MenuSnagKey: 0x80A,
|
||||
BootTimeDelayTenths: 0x80B,
|
||||
BusResetSeconds: 0x80C,
|
||||
CheckDeviceTenths: 0x80D,
|
||||
ConfigOptionBits: 0x80E,
|
||||
BlockOffsetDev0: 0x80F, // 3 bytes
|
||||
BlockOffsetDev1: 0x812, // 3 bytes
|
||||
Unused: 0x815
|
||||
};
|
||||
|
||||
// CFFA ATA Register Locations
|
||||
|
||||
const LOC = {
|
||||
ATADataHigh: 0x80,
|
||||
SetCSMask: 0x81,
|
||||
ClearCSMask: 0x82,
|
||||
WriteEEPROM: 0x83,
|
||||
NoWriteEEPROM: 0x84,
|
||||
ATADevCtrl: 0x86,
|
||||
ATAAltStatus: 0x86,
|
||||
ATADataLow: 0x88,
|
||||
AError: 0x89,
|
||||
ASectorCnt: 0x8a,
|
||||
ASector: 0x8b,
|
||||
ATACylinder: 0x8c,
|
||||
ATACylinderH: 0x8d,
|
||||
ATAHead: 0x8e,
|
||||
ATACommand: 0x8f,
|
||||
ATAStatus: 0x8f
|
||||
};
|
||||
|
||||
// ATA Status Bits
|
||||
|
||||
const STATUS = {
|
||||
BSY: 0x80, // Busy
|
||||
DRDY: 0x40, // Drive ready. 1 when ready
|
||||
DWF: 0x20, // Drive write fault. 1 when fault
|
||||
DSC: 0x10, // Disk seek complete. 1 when not seeking
|
||||
DRQ: 0x08, // Data request. 1 when ready to write
|
||||
CORR: 0x04, // Correct data. 1 on correctable error
|
||||
IDX: 0x02, // 1 once per revolution
|
||||
ERR: 0x01 // Error. 1 on error
|
||||
};
|
||||
|
||||
// ATA Identity Block Locations
|
||||
|
||||
const IDENTITY = {
|
||||
SectorCountLow: 58,
|
||||
SectorCountHigh: 57
|
||||
};
|
||||
|
||||
export default class CFFA implements Card, MassStorage {
|
||||
|
||||
// CFFA internal Flags
|
||||
|
||||
private _disableSignalling = false;
|
||||
private _writeEEPROM = true;
|
||||
|
||||
private _lba = true;
|
||||
|
||||
// LBA/CHS registers
|
||||
|
||||
private _sectorCnt = 1;
|
||||
private _sector = 0;
|
||||
private _cylinder = 0;
|
||||
private _cylinderH = 0;
|
||||
private _head = 0;
|
||||
private _drive = 0;
|
||||
|
||||
// CFFA Data High register
|
||||
|
||||
private _dataHigh = 0;
|
||||
|
||||
// Current Sector
|
||||
|
||||
private _curSector: Uint16Array | number[];
|
||||
private _curWord = 0;
|
||||
|
||||
// ATA Status registers
|
||||
|
||||
private _interruptsEnabled = false;
|
||||
private _altStatus = 0;
|
||||
private _error = 0;
|
||||
|
||||
private _identity: number[][] = [[], []];
|
||||
|
||||
// Disk data
|
||||
|
||||
private _partitions: any[] = [
|
||||
// Drive 1
|
||||
[],
|
||||
// Drive 2
|
||||
[]
|
||||
];
|
||||
|
||||
private _sectors: Uint16Array[][] = [
|
||||
// Drive 1
|
||||
[],
|
||||
// Drive 2
|
||||
[]
|
||||
];
|
||||
|
||||
constructor() {
|
||||
debug('CFFA');
|
||||
|
||||
for (let idx = 0; idx < 0x100; idx++) {
|
||||
this._identity[0][idx] = 0;
|
||||
this._identity[1][idx] = 0;
|
||||
}
|
||||
|
||||
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
|
||||
rom[SETTINGS.Max32MBPartitionsDev1] = 0x0;
|
||||
rom[SETTINGS.BootTimeDelayTenths] = 0x5; // 0.5 seconds
|
||||
rom[SETTINGS.CheckDeviceTenths] = 0x5; // 0.5 seconds
|
||||
}
|
||||
|
||||
// Verbose debug method
|
||||
|
||||
private _debug(..._args: any[]) {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
this._debug('reset');
|
||||
|
||||
this._sectorCnt = 1;
|
||||
this._sector = 0;
|
||||
this._cylinder = 0;
|
||||
this._cylinderH = 0;
|
||||
this._head = 0;
|
||||
this._drive = 0;
|
||||
|
||||
this._dataHigh = 0;
|
||||
}
|
||||
|
||||
// Convert status register into readable string
|
||||
|
||||
private _statusString(status: byte) {
|
||||
const statusArray = [];
|
||||
let flag: keyof typeof STATUS;
|
||||
for (flag in STATUS) {
|
||||
if(status & STATUS[flag]) {
|
||||
statusArray.push(flag);
|
||||
}
|
||||
}
|
||||
return statusArray.join('|');
|
||||
}
|
||||
|
||||
// Dump sector as hex and ascii
|
||||
|
||||
private _dumpSector(sector: number) {
|
||||
if (sector >= this._sectors[this._drive].length) {
|
||||
this._debug('dump sector out of range', sector);
|
||||
return;
|
||||
}
|
||||
for (let idx = 0; idx < 16; idx++) {
|
||||
const row = [];
|
||||
const charRow = [];
|
||||
for (let jdx = 0; jdx < 16; jdx++) {
|
||||
const val = this._sectors[this._drive][sector][idx * 16 + jdx];
|
||||
row.push(toHex(val, 4));
|
||||
const low = val & 0x7f;
|
||||
const hi = val >> 8 & 0x7f;
|
||||
charRow.push(low > 0x1f ? String.fromCharCode(low) : '.');
|
||||
charRow.push(hi > 0x1f ? String.fromCharCode(hi) : '.');
|
||||
}
|
||||
this._debug(row.join(' '), ' ', charRow.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
// Card I/O access
|
||||
|
||||
private _access(off: byte, val: byte) {
|
||||
const readMode = val === undefined;
|
||||
let retVal;
|
||||
let sector;
|
||||
|
||||
if (readMode) {
|
||||
retVal = 0;
|
||||
switch (off & 0x8f) {
|
||||
case LOC.ATADataHigh: // 0x00
|
||||
retVal = this._dataHigh;
|
||||
break;
|
||||
case LOC.SetCSMask: // 0x01
|
||||
this._disableSignalling = true;
|
||||
break;
|
||||
case LOC.ClearCSMask: // 0x02
|
||||
this._disableSignalling = false;
|
||||
break;
|
||||
case LOC.WriteEEPROM: // 0x03
|
||||
this._writeEEPROM = true;
|
||||
break;
|
||||
case LOC.NoWriteEEPROM: // 0x04
|
||||
this._writeEEPROM = false;
|
||||
break;
|
||||
case LOC.ATAAltStatus: // 0x06
|
||||
retVal = this._altStatus;
|
||||
break;
|
||||
case LOC.ATADataLow: // 0x08
|
||||
this._dataHigh = this._curSector[this._curWord] >> 8;
|
||||
retVal = this._curSector[this._curWord] & 0xff;
|
||||
if (!this._disableSignalling) {
|
||||
this._curWord++;
|
||||
}
|
||||
break;
|
||||
case LOC.AError: // 0x09
|
||||
retVal = this._error;
|
||||
break;
|
||||
case LOC.ASectorCnt: // 0x0A
|
||||
retVal = this._sectorCnt;
|
||||
break;
|
||||
case LOC.ASector: // 0x0B
|
||||
retVal = this._sector;
|
||||
break;
|
||||
case LOC.ATACylinder: // 0x0C
|
||||
retVal = this._cylinder;
|
||||
break;
|
||||
case LOC.ATACylinderH: // 0x0D
|
||||
retVal = this._cylinderH;
|
||||
break;
|
||||
case LOC.ATAHead: // 0x0E
|
||||
retVal = this._head | (this._lba ? 0x40 : 0) | (this._drive ? 0x10 : 0) | 0xA0;
|
||||
break;
|
||||
case LOC.ATAStatus: // 0x0F
|
||||
retVal = this._sectors[this._drive].length > 0 ? STATUS.DRDY | STATUS.DSC : 0;
|
||||
this._debug('returning status', this._statusString(retVal));
|
||||
break;
|
||||
default:
|
||||
debug('read unknown soft switch', toHex(off));
|
||||
}
|
||||
|
||||
if (off & 0x7) { // Anything but data high/low
|
||||
this._debug('read soft switch', toHex(off), toHex(retVal));
|
||||
}
|
||||
} else {
|
||||
if (off & 0x7) { // Anything but data high/low
|
||||
this._debug('write soft switch', toHex(off), toHex(val));
|
||||
}
|
||||
|
||||
switch (off & 0x8f) {
|
||||
case LOC.ATADataHigh: // 0x00
|
||||
this._dataHigh = val;
|
||||
break;
|
||||
case LOC.SetCSMask: // 0x01
|
||||
this._disableSignalling = true;
|
||||
break;
|
||||
case LOC.ClearCSMask: // 0x02
|
||||
this._disableSignalling = false;
|
||||
break;
|
||||
case LOC.WriteEEPROM: // 0x03
|
||||
this._writeEEPROM = true;
|
||||
break;
|
||||
case LOC.NoWriteEEPROM: // 0x04
|
||||
this._writeEEPROM = false;
|
||||
break;
|
||||
case LOC.ATADevCtrl: // 0x06
|
||||
this._debug('devCtrl:', toHex(val));
|
||||
this._interruptsEnabled = (val & 0x04) ? true : false;
|
||||
this._debug('Interrupts', this._interruptsEnabled ? 'enabled' : 'disabled');
|
||||
if (val & 0x02) {
|
||||
this._reset();
|
||||
}
|
||||
break;
|
||||
case LOC.ATADataLow: // 0x08
|
||||
this._curSector[this._curWord] = this._dataHigh << 8 | val;
|
||||
this._curWord++;
|
||||
break;
|
||||
case LOC.ASectorCnt: // 0x0a
|
||||
this._debug('setting sector count', val);
|
||||
this._sectorCnt = val;
|
||||
break;
|
||||
case LOC.ASector: // 0x0b
|
||||
this._debug('setting sector', toHex(val));
|
||||
this._sector = val;
|
||||
break;
|
||||
case LOC.ATACylinder: // 0x0c
|
||||
this._debug('setting cylinder', toHex(val));
|
||||
this._cylinder = val;
|
||||
break;
|
||||
case LOC.ATACylinderH: // 0x0d
|
||||
this._debug('setting cylinder high', toHex(val));
|
||||
this._cylinderH = val;
|
||||
break;
|
||||
case LOC.ATAHead:
|
||||
this._head = val & 0xf;
|
||||
this._lba = val & 0x40 ? true : false;
|
||||
this._drive = val & 0x10 ? 1 : 0;
|
||||
this._debug('setting head', toHex(val & 0xf), 'drive', this._drive);
|
||||
if (!this._lba) {
|
||||
console.error('CHS mode not supported');
|
||||
}
|
||||
break;
|
||||
case LOC.ATACommand: // 0x0f
|
||||
this._debug('command:', toHex(val));
|
||||
sector = this._head << 24 | this._cylinderH << 16 | this._cylinder << 8 | this._sector;
|
||||
this._dumpSector(sector);
|
||||
|
||||
switch (val) {
|
||||
case COMMANDS.ATAIdentify:
|
||||
this._debug('ATA identify');
|
||||
this._curSector = this._identity[this._drive];
|
||||
this._curWord = 0;
|
||||
break;
|
||||
case COMMANDS.ATACRead:
|
||||
this._debug('ATA read sector', toHex(this._cylinderH), toHex(this._cylinder), toHex(this._sector), sector);
|
||||
this._curSector = this._sectors[this._drive][sector];
|
||||
this._curWord = 0;
|
||||
break;
|
||||
case COMMANDS.ATACWrite:
|
||||
this._debug('ATA write sector', toHex(this._cylinderH), toHex(this._cylinder), toHex(this._sector), sector);
|
||||
this._curSector = this._sectors[this._drive][sector];
|
||||
this._curWord = 0;
|
||||
break;
|
||||
default:
|
||||
debug('unknown command', toHex(val));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug('write unknown soft switch', toHex(off), toHex(val));
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
ioSwitch(off: byte, val: byte) {
|
||||
return this._access(off, val);
|
||||
}
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
return rom[(page - 0xc0) << 8 | off];
|
||||
}
|
||||
|
||||
write(page: byte, off: byte, val: byte) {
|
||||
if (this._writeEEPROM) {
|
||||
this._debug('writing', toHex(page << 8 | off), toHex(val));
|
||||
rom[(page - 0xc0) << 8 | off] - val;
|
||||
}
|
||||
}
|
||||
|
||||
getState() {
|
||||
// TODO CFFA State
|
||||
return {};
|
||||
}
|
||||
|
||||
setState(_: any) {}
|
||||
|
||||
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
|
||||
|
||||
setBinary(drive: number, name: string, ext: DiskFormat, rawData: ArrayBuffer) {
|
||||
drive = drive - 1;
|
||||
const volume = 254;
|
||||
const readOnly = false;
|
||||
|
||||
if (ext === '2mg') {
|
||||
const { bytes, offset } = read2MGHeader(rawData);
|
||||
rawData = rawData.slice(offset, offset + bytes);
|
||||
}
|
||||
const options = {
|
||||
rawData,
|
||||
name,
|
||||
volume,
|
||||
readOnly
|
||||
};
|
||||
const disk = BlockVolume(options);
|
||||
|
||||
// Convert 512 byte blocks into 256 word sectors
|
||||
this._sectors[drive] = disk.blocks.map(function(block) {
|
||||
return new Uint16Array(block.buffer);
|
||||
});
|
||||
|
||||
this._identity[drive][IDENTITY.SectorCountHigh] = this._sectors[0].length & 0xffff;
|
||||
this._identity[drive][IDENTITY.SectorCountLow] = this._sectors[0].length >> 16;
|
||||
|
||||
const prodos = ProDOSVolume(disk);
|
||||
dump(prodos);
|
||||
|
||||
this._partitions[drive] = prodos;
|
||||
|
||||
if (drive) {
|
||||
rom[SETTINGS.Max32MBPartitionsDev1] = 0x1;
|
||||
} else {
|
||||
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -9,22 +9,40 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { base64_decode, base64_encode} from '../base64';
|
||||
import { bit, byte, Card, DiskFormat, MemberOf, memory, nibble, rom } from '../types';
|
||||
import { base64_encode} from '../base64';
|
||||
import type {
|
||||
bit,
|
||||
byte,
|
||||
Card,
|
||||
memory,
|
||||
nibble,
|
||||
rom,
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
FormatWorkerMessage,
|
||||
FormatWorkerResponse,
|
||||
DiskFormat,
|
||||
DiskProcessedType,
|
||||
DRIVE_NUMBERS,
|
||||
DriveNumber,
|
||||
JSONDisk,
|
||||
ProcessBinaryType,
|
||||
ProcessJsonDiskType,
|
||||
ProcessJsonType,
|
||||
} from '../formats/types';
|
||||
|
||||
import {
|
||||
createDisk,
|
||||
createDiskFromJsonDisk
|
||||
} from '../formats/create_disk';
|
||||
|
||||
import { debug, toHex } from '../util';
|
||||
import { Disk, JSONDisk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||
import { jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||
|
||||
import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2';
|
||||
|
||||
import _2MG from '../formats/2mg';
|
||||
import D13 from '../formats/d13';
|
||||
import DOS from '../formats/do';
|
||||
import ProDOS from '../formats/po';
|
||||
import Woz from '../formats/woz';
|
||||
import Nibble from '../formats/nib';
|
||||
import Apple2IO from '../apple2io';
|
||||
|
||||
|
||||
/** Softswitch locations */
|
||||
const LOC = {
|
||||
// Disk II Controller Commands
|
||||
|
@ -144,10 +162,6 @@ const PHASE_DELTA = [
|
|||
[-2, -1, 0, 1],
|
||||
[1, -2, -1, 0]
|
||||
] as const;
|
||||
|
||||
export const DRIVE_NUMBERS = [1, 2] as const;
|
||||
export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
|
||||
|
||||
export interface Callbacks {
|
||||
driveLight: (drive: DriveNumber, on: boolean) => void;
|
||||
dirty: (drive: DriveNumber, dirty: boolean) => void;
|
||||
|
@ -155,6 +169,7 @@ export interface Callbacks {
|
|||
}
|
||||
|
||||
/** Common information for Nibble and WOZ disks. */
|
||||
|
||||
interface BaseDrive {
|
||||
/** Current disk format. */
|
||||
format: DiskFormat,
|
||||
|
@ -176,6 +191,8 @@ interface BaseDrive {
|
|||
|
||||
/** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */
|
||||
interface WozDrive extends BaseDrive {
|
||||
/** Woz encoding */
|
||||
encoding: 'woz'
|
||||
/** Maps quarter tracks to data in rawTracks; `0xFF` = random garbage. */
|
||||
trackMap: byte[];
|
||||
/** Unique track bitstreams. The index is arbitrary; it is NOT the track number. */
|
||||
|
@ -184,19 +201,25 @@ interface WozDrive extends BaseDrive {
|
|||
|
||||
/** Nibble format track data. */
|
||||
interface NibbleDrive extends BaseDrive {
|
||||
/** Nibble encoding */
|
||||
encoding: 'nibble'
|
||||
/** Nibble data. The index is the track number. */
|
||||
tracks: memory[];
|
||||
}
|
||||
|
||||
type Drive = WozDrive | NibbleDrive;
|
||||
|
||||
function isNibbleDrive(drive: Drive): drive is NibbleDrive {
|
||||
return 'tracks' in drive;
|
||||
function isNibbleDrive(drive: Drive): drive is NibbleDrive {
|
||||
return drive.encoding === 'nibble';
|
||||
}
|
||||
|
||||
function isWozDrive(drive: Drive): drive is WozDrive {
|
||||
return drive.encoding === 'woz';
|
||||
}
|
||||
|
||||
// Does not support WOZ disks
|
||||
interface DriveState {
|
||||
format: DiskFormat,
|
||||
encoding: 'nibble' | 'woz'
|
||||
volume: byte,
|
||||
name: string,
|
||||
tracks: memory[],
|
||||
|
@ -205,6 +228,8 @@ interface DriveState {
|
|||
phase: Phase,
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
trackMap: number[],
|
||||
rawTracks: bit[][],
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -216,10 +241,10 @@ interface State {
|
|||
drive: DriveNumber;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
function getDriveState(drive: Drive): DriveState {
|
||||
const result: DriveState = {
|
||||
format: drive.format,
|
||||
encoding: drive.encoding,
|
||||
volume: drive.volume,
|
||||
name: drive.name,
|
||||
tracks: [],
|
||||
|
@ -227,34 +252,61 @@ function getDriveState(drive: Drive): DriveState {
|
|||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
dirty: drive.dirty,
|
||||
trackMap: [],
|
||||
rawTracks: [],
|
||||
};
|
||||
if (!isNibbleDrive(drive)) {
|
||||
throw Error('No tracks.');
|
||||
|
||||
if (isNibbleDrive(drive)) {
|
||||
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
||||
result.tracks.push(new Uint8Array(drive.tracks[idx]));
|
||||
}
|
||||
}
|
||||
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
||||
result.tracks.push(new Uint8Array(drive.tracks[idx]));
|
||||
if (isWozDrive(drive)) {
|
||||
result.trackMap = [...drive.trackMap];
|
||||
for (let idx = 0; idx < drive.rawTracks.length; idx++) {
|
||||
result.rawTracks.push([...drive.rawTracks[idx]]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
function setDriveState(state: DriveState) {
|
||||
const result: Drive = {
|
||||
format: state.format,
|
||||
volume: state.volume,
|
||||
name: state.name,
|
||||
tracks: [] as memory[],
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
readOnly: state.readOnly,
|
||||
dirty: state.dirty
|
||||
};
|
||||
for (let idx = 0; idx < state.tracks.length; idx++) {
|
||||
result.tracks!.push(new Uint8Array(state.tracks[idx]));
|
||||
let result: Drive;
|
||||
if (state.encoding === 'nibble') {
|
||||
result = {
|
||||
format: state.format,
|
||||
encoding: 'nibble',
|
||||
volume: state.volume,
|
||||
name: state.name,
|
||||
tracks: [],
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
readOnly: state.readOnly,
|
||||
dirty: state.dirty,
|
||||
};
|
||||
for (let idx = 0; idx < state.tracks.length; idx++) {
|
||||
result.tracks.push(new Uint8Array(state.tracks[idx]));
|
||||
}
|
||||
} else {
|
||||
result = {
|
||||
format: state.format,
|
||||
encoding: 'woz',
|
||||
volume: state.volume,
|
||||
name: state.name,
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
readOnly: state.readOnly,
|
||||
dirty: state.dirty,
|
||||
trackMap: [...state.trackMap],
|
||||
rawTracks: [],
|
||||
};
|
||||
for (let idx = 0; idx < state.rawTracks.length; idx++) {
|
||||
result.rawTracks.push([...state.rawTracks[idx]]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -266,6 +318,7 @@ export default class DiskII implements Card {
|
|||
private drives: Drive[] = [
|
||||
{ // Drive 1
|
||||
format: 'dsk',
|
||||
encoding: 'nibble',
|
||||
volume: 254,
|
||||
name: 'Disk 1',
|
||||
tracks: [],
|
||||
|
@ -273,10 +326,11 @@ export default class DiskII implements Card {
|
|||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
dirty: false,
|
||||
},
|
||||
{ // Drive 2
|
||||
format: 'dsk',
|
||||
encoding: 'nibble',
|
||||
volume: 254,
|
||||
name: 'Disk 2',
|
||||
tracks: [],
|
||||
|
@ -284,7 +338,7 @@ export default class DiskII implements Card {
|
|||
head: 0,
|
||||
phase: 0,
|
||||
readOnly: false,
|
||||
dirty: false
|
||||
dirty: false,
|
||||
}];
|
||||
|
||||
private skip = 0;
|
||||
|
@ -328,26 +382,26 @@ export default class DiskII implements Card {
|
|||
/** Contents of the P6 ROM. */
|
||||
private sequencerRom: typeof SEQUENCER_ROM_16 | typeof SEQUENCER_ROM_13;
|
||||
|
||||
private worker: Worker;
|
||||
|
||||
/** Builds a new Disk ][ card. */
|
||||
constructor(private io: Apple2IO, private callbacks: Callbacks, private sectors = 16) {
|
||||
this.debug('Disk ][');
|
||||
|
||||
this.lastCycles = this.io.cycles();
|
||||
this.bootstrapRom = this.sectors == 16 ? BOOTSTRAP_ROM_16 : BOOTSTRAP_ROM_13;
|
||||
this.sequencerRom = this.sectors == 16 ? SEQUENCER_ROM_16 : SEQUENCER_ROM_13;
|
||||
|
||||
this.init();
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
private debug(..._args: any[]) {
|
||||
// debug.apply(this, arguments);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.debug('Disk ][');
|
||||
}
|
||||
|
||||
// Only used for WOZ disks
|
||||
private moveHead() {
|
||||
if (isNibbleDrive(this.cur)) {
|
||||
if (!isWozDrive(this.cur)) {
|
||||
return;
|
||||
}
|
||||
const track: bit[] =
|
||||
|
@ -665,7 +719,6 @@ export default class DiskII implements Card {
|
|||
this.moveHead();
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
getState() {
|
||||
const result = {
|
||||
drives: [] as DriveState[],
|
||||
|
@ -682,7 +735,6 @@ export default class DiskII implements Card {
|
|||
return result;
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
setState(state: State) {
|
||||
this.skip = state.skip;
|
||||
this.latch = state.latch;
|
||||
|
@ -699,22 +751,17 @@ export default class DiskII implements Card {
|
|||
this.cur = this.drives[this.drive - 1];
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work for WOZ disks
|
||||
getMetadata(driveNo: DriveNumber) {
|
||||
const drive = this.drives[driveNo - 1];
|
||||
if (isNibbleDrive(drive)) {
|
||||
return {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work on WOZ disks
|
||||
|
@ -728,63 +775,28 @@ export default class DiskII implements Card {
|
|||
|
||||
/** Sets the data for `drive` from `disk`, which is expected to be JSON. */
|
||||
// TODO(flan): This implementation is not very safe.
|
||||
setDisk(drive: DriveNumber, disk: JSONDisk) {
|
||||
const fmt = disk.type;
|
||||
const readOnly = disk.readOnly;
|
||||
const name = disk.name;
|
||||
|
||||
let data: memory[] | memory[][];
|
||||
if (disk.encoding == 'base64') {
|
||||
data = [];
|
||||
for (let t = 0; t < disk.data.length; t++) {
|
||||
if (fmt == 'nib') {
|
||||
data[t] = base64_decode(disk.data[t] as string);
|
||||
} else {
|
||||
data[t] = [];
|
||||
for (let s = 0; s < disk.data[t].length; s++) {
|
||||
data[t][s] = base64_decode(disk.data[t][s] as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
setDisk(drive: DriveNumber, jsonDisk: JSONDisk) {
|
||||
if (this.worker) {
|
||||
const message: FormatWorkerMessage = {
|
||||
type: ProcessJsonDiskType,
|
||||
payload: {
|
||||
drive,
|
||||
jsonDisk
|
||||
},
|
||||
};
|
||||
this.worker.postMessage(message);
|
||||
return true;
|
||||
} else {
|
||||
data = disk.data;
|
||||
const disk = createDiskFromJsonDisk(jsonDisk);
|
||||
if (disk) {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, disk);
|
||||
this.updateDirty(drive, false);
|
||||
this.callbacks.label(drive, disk.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const cur = this.drives[drive - 1];
|
||||
|
||||
// var v = (fmt === 'dsk' ? data[0x11][0x00][0x06] : 0xfe);
|
||||
// if (v == 0x00) {
|
||||
const volume = disk.volume || 0xfe;
|
||||
// }
|
||||
|
||||
const options = {
|
||||
volume,
|
||||
readOnly,
|
||||
name,
|
||||
data
|
||||
};
|
||||
|
||||
let newDisk: Disk;
|
||||
switch (fmt) {
|
||||
case 'd13':
|
||||
newDisk = D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
newDisk = DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
newDisk = Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
newDisk = ProDOS(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(cur, newDisk);
|
||||
this.updateDirty(drive, false);
|
||||
this.callbacks.label(drive, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
getJSON(drive: DriveNumber, pretty: boolean = false) {
|
||||
|
@ -795,52 +807,77 @@ export default class DiskII implements Card {
|
|||
return jsonEncode(cur, pretty);
|
||||
}
|
||||
|
||||
setJSON(drive: DriveNumber, data: string) {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, jsonDecode(data));
|
||||
setJSON(drive: DriveNumber, json: string) {
|
||||
if (this.worker) {
|
||||
const message: FormatWorkerMessage = {
|
||||
type: ProcessJsonType,
|
||||
payload: {
|
||||
drive,
|
||||
json
|
||||
},
|
||||
};
|
||||
this.worker.postMessage(message);
|
||||
} else {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, jsonDecode(json));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
setBinary(drive: DriveNumber, name: string, fmt: DiskFormat, rawData: ArrayBuffer) {
|
||||
let disk;
|
||||
const cur = this.drives[drive - 1];
|
||||
const readOnly = false;
|
||||
const volume = 254;
|
||||
const options = {
|
||||
name,
|
||||
rawData,
|
||||
readOnly,
|
||||
volume
|
||||
volume,
|
||||
};
|
||||
|
||||
switch (fmt) {
|
||||
case '2mg':
|
||||
disk = _2MG(options);
|
||||
break;
|
||||
case 'd13':
|
||||
disk = D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
disk = DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
disk = Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
disk = ProDOS(options);
|
||||
break;
|
||||
case 'woz':
|
||||
disk = Woz(options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (this.worker) {
|
||||
const message: FormatWorkerMessage = {
|
||||
type: ProcessBinaryType,
|
||||
payload: {
|
||||
drive,
|
||||
fmt,
|
||||
options,
|
||||
}
|
||||
};
|
||||
this.worker.postMessage(message);
|
||||
|
||||
Object.assign(cur, disk);
|
||||
this.updateDirty(drive, true);
|
||||
this.callbacks.label(this.drive, name);
|
||||
return true;
|
||||
return true;
|
||||
} else {
|
||||
const disk = createDisk(fmt, options);
|
||||
if (disk) {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, disk);
|
||||
this.updateDirty(drive, true);
|
||||
this.callbacks.label(this.drive, name);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
initWorker() {
|
||||
this.worker = new Worker('dist/format_worker.bundle.js');
|
||||
|
||||
this.worker.addEventListener('message', (message: MessageEvent<FormatWorkerResponse>) => {
|
||||
const { data } = message;
|
||||
switch (data.type) {
|
||||
case DiskProcessedType: {
|
||||
const { drive, disk } = data.payload;
|
||||
if (disk) {
|
||||
const cur = this.drives[drive - 1];
|
||||
Object.assign(cur, disk);
|
||||
this.updateDirty(drive, true);
|
||||
this.callbacks.label(this.drive, disk.name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work with WOZ disks
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
import { debug, toHex } from '../util';
|
||||
import { rom as smartPortRom } from '../roms/cards/smartport';
|
||||
import { Card, Restorable, byte, word, rom } from '../types';
|
||||
import { MassStorage } from '../formats/types';
|
||||
import CPU6502, { CpuState, flags } from '../cpu6502';
|
||||
import { read2MGHeader } from '../formats/2mg';
|
||||
|
||||
type SmartDisk = Uint8Array[];
|
||||
|
||||
|
@ -100,7 +102,7 @@ const ADDRESS_LO = 0x44;
|
|||
const BLOCK_LO = 0x46;
|
||||
// const BLOCK_HI = 0x47;
|
||||
|
||||
export default class SmartPort implements Card, Restorable<SmartPortState> {
|
||||
export default class SmartPort implements Card, MassStorage, Restorable<SmartPortState> {
|
||||
|
||||
private rom: rom;
|
||||
private disks: SmartDisk[] = [];
|
||||
|
@ -451,7 +453,8 @@ export default class SmartPort implements Card, Restorable<SmartPortState> {
|
|||
setBinary(drive: number, _name: string, fmt: string, data: ArrayBuffer) {
|
||||
this.disks[drive] = [];
|
||||
if (fmt == '2mg') {
|
||||
data = data.slice(64);
|
||||
const { bytes, offset } = read2MGHeader(data);
|
||||
data = data.slice(offset, offset + bytes);
|
||||
}
|
||||
for (let idx = 0; idx < data.byteLength; idx += 512) {
|
||||
this.disks[drive].push(new Uint8Array(data.slice(idx, idx + 512)));
|
||||
|
|
|
@ -1,87 +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 BlockVolume from './block';
|
||||
import DOS from './do';
|
||||
import Nibble from './nib';
|
||||
import ProDOS from './po';
|
||||
|
||||
import { numToString, debug } from '../util';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from a 2mg image.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function _2MG(options) {
|
||||
var OFFSETS = {
|
||||
CREATOR: 0x04,
|
||||
FLAGS: 0x0A,
|
||||
FORMAT: 0x0C,
|
||||
BLOCKS: 0x14,
|
||||
DATA_OFFSET: 0x18,
|
||||
BYTES: 0x1C,
|
||||
};
|
||||
|
||||
var FLAGS = {
|
||||
READ_ONLY: 0x80000000,
|
||||
VOLUME_VALID: 0x00000100,
|
||||
VOLUME_MASK: 0x000000FF
|
||||
};
|
||||
|
||||
var { rawData, arrayConstructor } = options;
|
||||
var disk;
|
||||
var volume = 254;
|
||||
|
||||
// Standard header size is 64 bytes. Make assumptions.
|
||||
var prefix = new DataView(rawData);
|
||||
var signature = numToString(prefix.getInt32(0x0, true));
|
||||
if (signature !== '2IMG') {
|
||||
throw new Error('Unrecognized 2mg signature: ' + signature);
|
||||
}
|
||||
var creator = numToString(prefix.getInt32(OFFSETS.CREATOR, true));
|
||||
var format = prefix.getInt32(OFFSETS.FORMAT, true);
|
||||
var bytes = prefix.getInt32(OFFSETS.BYTES, true);
|
||||
var offset = prefix.getInt32(OFFSETS.DATA_OFFSET, true);
|
||||
var flags = prefix.getInt32(OFFSETS.FLAGS, true);
|
||||
var readOnly = (flags & FLAGS.READ_ONLY) !== 0;
|
||||
if (flags & FLAGS.VOLUME_VALID) {
|
||||
volume = flags & FLAGS.VOLUME_MASK;
|
||||
}
|
||||
|
||||
debug('created by', creator);
|
||||
rawData = rawData.slice(offset, offset + bytes);
|
||||
|
||||
var blockVolume = options.blockVolume || rawData.byteLength >= (800 * 1024);
|
||||
|
||||
options = { rawData, readOnly, volume, arrayConstructor };
|
||||
|
||||
if (blockVolume) {
|
||||
disk = new BlockVolume(options);
|
||||
} else {
|
||||
// Check image format.
|
||||
// Sure, it's really 64 bits. But only 2 are actually used.
|
||||
switch (format) {
|
||||
case 1: // PO
|
||||
disk = new ProDOS(options);
|
||||
break;
|
||||
case 2: // NIB
|
||||
disk = new Nibble(options);
|
||||
break;
|
||||
case 0: // dsk
|
||||
default: // Something hinky, assume 'dsk'
|
||||
disk = new DOS(options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return disk;
|
||||
}
|
103
js/formats/2mg.ts
Normal file
103
js/formats/2mg.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import DOS from './do';
|
||||
import Nibble from './nib';
|
||||
import ProDOS from './po';
|
||||
import { DiskOptions } from './types';
|
||||
|
||||
import { numToString, debug } from '../util';
|
||||
|
||||
export interface _2MGOptions {
|
||||
blockVolume?: boolean
|
||||
rawData: ArrayBuffer
|
||||
readOnly: boolean
|
||||
arrayConstructor: any
|
||||
}
|
||||
|
||||
const OFFSETS = {
|
||||
CREATOR: 0x04,
|
||||
FLAGS: 0x0A,
|
||||
FORMAT: 0x0C,
|
||||
BLOCKS: 0x14,
|
||||
DATA_OFFSET: 0x18,
|
||||
BYTES: 0x1C,
|
||||
};
|
||||
|
||||
const FLAGS = {
|
||||
READ_ONLY: 0x80000000,
|
||||
VOLUME_VALID: 0x00000100,
|
||||
VOLUME_MASK: 0x000000FF
|
||||
};
|
||||
|
||||
export function read2MGHeader(rawData: ArrayBuffer) {
|
||||
const prefix = new DataView(rawData);
|
||||
const signature = numToString(prefix.getInt32(0x0, true));
|
||||
if (signature !== '2IMG') {
|
||||
throw new Error('Unrecognized 2mg signature: ' + signature);
|
||||
}
|
||||
const creator = numToString(prefix.getInt32(OFFSETS.CREATOR, true));
|
||||
const format = prefix.getInt32(OFFSETS.FORMAT, true);
|
||||
const bytes = prefix.getInt32(OFFSETS.BYTES, true);
|
||||
const offset = prefix.getInt32(OFFSETS.DATA_OFFSET, true);
|
||||
const flags = prefix.getInt32(OFFSETS.FLAGS, true);
|
||||
const readOnly = (flags & FLAGS.READ_ONLY) !== 0;
|
||||
let volume = 254;
|
||||
if (flags & FLAGS.VOLUME_VALID) {
|
||||
volume = flags & FLAGS.VOLUME_MASK;
|
||||
}
|
||||
|
||||
debug('created by', creator);
|
||||
rawData = rawData.slice(offset, offset + bytes);
|
||||
|
||||
return {
|
||||
bytes,
|
||||
format,
|
||||
offset,
|
||||
readOnly,
|
||||
volume,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from a 2mg image.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function _2MG(options: DiskOptions) {
|
||||
let { rawData } = options;
|
||||
let disk;
|
||||
|
||||
if (!rawData) {
|
||||
throw new Error('Requires rawData');
|
||||
}
|
||||
|
||||
const { bytes, format, offset, readOnly, volume } = read2MGHeader(rawData);
|
||||
rawData = rawData.slice(offset, offset + bytes);
|
||||
options = { ...options, rawData, readOnly, volume };
|
||||
|
||||
// Check image format.
|
||||
// Sure, it's really 64 bits. But only 2 are actually used.
|
||||
switch (format) {
|
||||
case 1: // PO
|
||||
disk = ProDOS(options);
|
||||
break;
|
||||
case 2: // NIB
|
||||
disk = Nibble(options);
|
||||
break;
|
||||
case 0: // dsk
|
||||
default: // Something hinky, assume 'dsk'
|
||||
disk = DOS(options);
|
||||
break;
|
||||
}
|
||||
|
||||
return disk;
|
||||
}
|
|
@ -9,24 +9,29 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { DiskOptions } from './types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object for a block volume with block-ordered data.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function BlockVolume(options) {
|
||||
var { rawData, readOnly, name } = options;
|
||||
var disk;
|
||||
export default function BlockVolume(options: DiskOptions) {
|
||||
const { rawData, readOnly, name } = options;
|
||||
|
||||
var blocks = [];
|
||||
if (!rawData) {
|
||||
throw new Error('Requires rawData');
|
||||
}
|
||||
|
||||
let blocks = [];
|
||||
blocks = [];
|
||||
var offset = 0;
|
||||
let offset = 0;
|
||||
while (offset < rawData.byteLength) {
|
||||
blocks.push(new Uint8Array(rawData.slice(offset, offset + 0x200)));
|
||||
offset += 0x200;
|
||||
}
|
||||
|
||||
disk = {
|
||||
const disk: { blocks: Uint8Array[], name: string, readOnly: boolean } = {
|
||||
blocks,
|
||||
name,
|
||||
readOnly,
|
72
js/formats/create_disk.ts
Normal file
72
js/formats/create_disk.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import type { memory } from '../types';
|
||||
import { base64_decode } from '../base64';
|
||||
import type { Disk, DiskFormat, DiskOptions, JSONDisk } from './types';
|
||||
import _2MG from './2mg';
|
||||
import D13 from './d13';
|
||||
import DOS from './do';
|
||||
import ProDOS from './po';
|
||||
import Woz from './woz';
|
||||
import Nibble from './nib';
|
||||
|
||||
export function createDisk(fmt: DiskFormat, options: DiskOptions) {
|
||||
let disk: Disk | null = null;
|
||||
|
||||
switch (fmt) {
|
||||
case '2mg':
|
||||
disk = _2MG(options) as Disk;
|
||||
break;
|
||||
case 'd13':
|
||||
disk = D13(options);
|
||||
break;
|
||||
case 'do':
|
||||
case 'dsk':
|
||||
disk = DOS(options);
|
||||
break;
|
||||
case 'nib':
|
||||
disk = Nibble(options);
|
||||
break;
|
||||
case 'po':
|
||||
disk = ProDOS(options);
|
||||
break;
|
||||
case 'woz':
|
||||
disk = Woz(options);
|
||||
break;
|
||||
}
|
||||
|
||||
return disk;
|
||||
}
|
||||
|
||||
export function createDiskFromJsonDisk(disk: JSONDisk) {
|
||||
const fmt = disk.type;
|
||||
const readOnly = disk.readOnly;
|
||||
const name = disk.name;
|
||||
|
||||
let trackData: memory[][];
|
||||
if (disk.encoding == 'base64') {
|
||||
trackData = [];
|
||||
for (let t = 0; t < disk.data.length; t++) {
|
||||
trackData[t] = [];
|
||||
if (fmt == 'nib') {
|
||||
trackData[t][0] = base64_decode(disk.data[t] as string);
|
||||
} else {
|
||||
for (let s = 0; s < disk.data[t].length; s++) {
|
||||
trackData[t][s] = base64_decode(disk.data[t][s] as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trackData = disk.data;
|
||||
}
|
||||
|
||||
const volume = disk.volume || 0xfe;
|
||||
|
||||
const options = {
|
||||
volume,
|
||||
readOnly,
|
||||
name,
|
||||
data: trackData
|
||||
} as DiskOptions;
|
||||
|
||||
return createDisk(fmt, options);
|
||||
}
|
||||
|
|
@ -10,50 +10,56 @@
|
|||
*/
|
||||
|
||||
import { explodeSector13, D13O } from './format_utils';
|
||||
import type { NibbleDisk, DiskOptions } from './types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from DOS 3.2-ordered image data.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function DOS13(options) {
|
||||
var { data, name, rawData, volume, readOnly } = options;
|
||||
var disk = {
|
||||
export default function DOS13(options: DiskOptions) {
|
||||
const { data, name, rawData, volume, readOnly } = options;
|
||||
const disk: NibbleDisk = {
|
||||
format: 'd13',
|
||||
encoding: 'nibble',
|
||||
name,
|
||||
volume,
|
||||
readOnly,
|
||||
tracks: [],
|
||||
trackMap: null,
|
||||
rawTracks: null
|
||||
tracks: []
|
||||
};
|
||||
|
||||
if (!data && !rawData) {
|
||||
throw new Error('data or rawData required');
|
||||
}
|
||||
|
||||
/*
|
||||
* DOS 13-sector disks have the physical sectors skewed on the track. The skew
|
||||
* between physical sectors is 10 (A), resulting in the following physical order:
|
||||
*
|
||||
*
|
||||
* 0 A 7 4 1 B 8 5 2 C 9 6 3
|
||||
*
|
||||
*
|
||||
* Note that because physical sector == logical sector, this works slightly
|
||||
* differently from the DOS and ProDOS nibblizers.
|
||||
*/
|
||||
|
||||
for (var t = 0; t < 35; t++) {
|
||||
var track = [];
|
||||
for (var disk_sector = 0; disk_sector < 13; disk_sector++) {
|
||||
var physical_sector = D13O[disk_sector];
|
||||
var sector;
|
||||
for (let t = 0; t < 35; t++) {
|
||||
let track: number[] = [];
|
||||
for (let disk_sector = 0; disk_sector < 13; disk_sector++) {
|
||||
const physical_sector = D13O[disk_sector];
|
||||
let sector: Uint8Array;
|
||||
if (rawData) {
|
||||
var off = (13 * t + physical_sector) * 256;
|
||||
const off = (13 * t + physical_sector) * 256;
|
||||
sector = new Uint8Array(rawData.slice(off, off + 256));
|
||||
} else {
|
||||
} else if (data) {
|
||||
sector = data[t][physical_sector];
|
||||
} else {
|
||||
throw new Error('Requires data or rawData');
|
||||
}
|
||||
track = track.concat(
|
||||
explodeSector13(volume, t, physical_sector, sector)
|
||||
);
|
||||
}
|
||||
disk.tracks.push(track);
|
||||
disk.tracks.push(new Uint8Array(track));
|
||||
}
|
||||
|
||||
return disk;
|
|
@ -11,34 +11,37 @@
|
|||
|
||||
import { explodeSector16, DO } from './format_utils';
|
||||
import { bytify } from '../util';
|
||||
import { byte } from '../types';
|
||||
import { NibbleDisk, DiskOptions } from './types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from DOS-ordered image data.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function DOS(options) {
|
||||
var { data, name, rawData, volume, readOnly } = options;
|
||||
var disk = {
|
||||
export default function DOS(options: DiskOptions) {
|
||||
const { data, name, rawData, volume, readOnly } = options;
|
||||
const disk: NibbleDisk = {
|
||||
format: 'dsk',
|
||||
encoding: 'nibble',
|
||||
name,
|
||||
volume,
|
||||
readOnly,
|
||||
tracks: [],
|
||||
trackMap: null,
|
||||
rawTracks: null
|
||||
};
|
||||
|
||||
for (var t = 0; t < 35; t++) {
|
||||
var track = [];
|
||||
for (var physical_sector = 0; physical_sector < 16; physical_sector++) {
|
||||
for (let t = 0; t < 35; t++) {
|
||||
let track: byte[] = [];
|
||||
for (let physical_sector = 0; physical_sector < 16; physical_sector++) {
|
||||
const dos_sector = DO[physical_sector];
|
||||
var sector;
|
||||
let sector: Uint8Array;
|
||||
if (rawData) {
|
||||
const off = (16 * t + dos_sector) * 256;
|
||||
sector = new Uint8Array(rawData.slice(off, off + 256));
|
||||
} else if (data) {
|
||||
sector = new Uint8Array(data[t][dos_sector]);
|
||||
} else {
|
||||
sector = data[t][dos_sector];
|
||||
throw new Error('Requires data or rawData');
|
||||
}
|
||||
track = track.concat(
|
||||
explodeSector16(volume, t, physical_sector, sector)
|
|
@ -9,64 +9,10 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { byte, DiskFormat, memory } from '../types';
|
||||
import { byte, memory } from '../types';
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { bytify, debug, toHex } from '../util';
|
||||
import { GamepadConfiguration } from '../ui/gamepad';
|
||||
|
||||
export interface Disk {
|
||||
format: DiskFormat
|
||||
name: string
|
||||
volume: byte
|
||||
tracks: memory[]
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Base format for JSON defined disks
|
||||
*/
|
||||
export class JSONDiskBase {
|
||||
type: DiskFormat
|
||||
name: string
|
||||
disk?: number
|
||||
category?: string
|
||||
writeProtected?: boolean
|
||||
volume: byte
|
||||
readOnly: boolean
|
||||
gamepad?: GamepadConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
import { NibbleDisk } from './types';
|
||||
|
||||
/**
|
||||
* DOS 3.3 Physical sector order (index is physical sector, value is DOS sector).
|
||||
|
@ -255,7 +201,7 @@ export function explodeSector16(volume: byte, track: byte, sector: byte, data: m
|
|||
return buf;
|
||||
}
|
||||
|
||||
export function explodeSector13(volume: byte, track: byte, sector: byte, data: byte[]) {
|
||||
export function explodeSector13(volume: byte, track: byte, sector: byte, data: memory) {
|
||||
let buf = [];
|
||||
let gap;
|
||||
|
||||
|
@ -355,7 +301,7 @@ export function explodeSector13(volume: byte, track: byte, sector: byte, data: b
|
|||
}
|
||||
|
||||
// TODO(flan): Does not work on WOZ disks
|
||||
export function readSector(drive: Drive, track: byte, sector: byte) {
|
||||
export function readSector(drive: NibbleDisk, track: byte, sector: byte) {
|
||||
const _sector = drive.format == 'po' ? _PO[sector] : _DO[sector];
|
||||
let val, state = 0;
|
||||
let idx = 0;
|
||||
|
@ -450,7 +396,7 @@ export function readSector(drive: Drive, track: byte, sector: byte) {
|
|||
return new Uint8Array();
|
||||
}
|
||||
|
||||
export function jsonEncode(cur: Drive, pretty: boolean) {
|
||||
export function jsonEncode(cur: NibbleDisk, pretty: boolean) {
|
||||
// For 'nib', tracks are encoded as strings. For all other formats,
|
||||
// tracks are arrays of sectors which are encoded as strings.
|
||||
const data: string[] | string[][] = [];
|
||||
|
@ -490,12 +436,13 @@ export function jsonDecode(data: string) {
|
|||
}
|
||||
tracks[t] = bytify(track);
|
||||
}
|
||||
const cur: Drive = {
|
||||
const cur: NibbleDisk = {
|
||||
volume: v,
|
||||
format: json.type,
|
||||
encoding: 'nibble',
|
||||
name: json.name,
|
||||
tracks,
|
||||
readOnly,
|
||||
dirty: false,
|
||||
};
|
||||
|
||||
return cur;
|
||||
|
|
|
@ -9,30 +9,34 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { NibbleDisk, DiskOptions } from './types';
|
||||
import { memory } from '../types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from raw nibble image data.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function Nibble(options) {
|
||||
var { data, name, rawData, volume, readOnly } = options;
|
||||
var disk = {
|
||||
export default function Nibble(options: DiskOptions) {
|
||||
const { data, name, rawData, volume, readOnly } = options;
|
||||
const disk: NibbleDisk = {
|
||||
format: 'nib',
|
||||
encoding: 'nibble',
|
||||
name,
|
||||
volume: volume || 254,
|
||||
readOnly: readOnly || false,
|
||||
tracks: [],
|
||||
trackMap: null,
|
||||
rawTracks: null
|
||||
tracks: []
|
||||
};
|
||||
|
||||
for (var t = 0; t < 35; t++) {
|
||||
var track;
|
||||
for (let t = 0; t < 35; t++) {
|
||||
let track: memory;
|
||||
if (rawData) {
|
||||
var off = t * 0x1a00;
|
||||
track = new Uint8Array(data.slice(off, off + 0x1a00));
|
||||
const off = t * 0x1a00;
|
||||
track = new Uint8Array(rawData.slice(off, off + 0x1a00));
|
||||
} else if (data) {
|
||||
track = data[t][0];
|
||||
} else {
|
||||
track = data[t];
|
||||
throw new Error('Requires data or rawData');
|
||||
}
|
||||
disk.tracks[t] = track;
|
||||
}
|
|
@ -11,34 +11,37 @@
|
|||
|
||||
import { explodeSector16, PO } from './format_utils';
|
||||
import { bytify } from '../util';
|
||||
import type { byte } from '../types';
|
||||
import type { NibbleDisk, DiskOptions } from './types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object from ProDOS-ordered image data.
|
||||
* @param {*} options the disk image and options
|
||||
* @returns {import('./format_utils').Disk}
|
||||
*/
|
||||
export default function ProDOS(options) {
|
||||
var { data, name, rawData, volume, readOnly } = options;
|
||||
var disk = {
|
||||
export default function ProDOS(options: DiskOptions) {
|
||||
const { data, name, rawData, volume, readOnly } = options;
|
||||
const disk: NibbleDisk = {
|
||||
format: 'nib',
|
||||
encoding: 'nibble',
|
||||
name,
|
||||
volume: volume || 254,
|
||||
tracks: [],
|
||||
readOnly: readOnly || false,
|
||||
trackMap: null,
|
||||
rawTracks: null
|
||||
};
|
||||
|
||||
for (var physical_track = 0; physical_track < 35; physical_track++) {
|
||||
var track = [];
|
||||
for (var physical_sector = 0; physical_sector < 16; physical_sector++) {
|
||||
for (let physical_track = 0; physical_track < 35; physical_track++) {
|
||||
let track: byte[] = [];
|
||||
for (let physical_sector = 0; physical_sector < 16; physical_sector++) {
|
||||
const prodos_sector = PO[physical_sector];
|
||||
var sector;
|
||||
let sector;
|
||||
if (rawData) {
|
||||
var off = (16 * physical_track + prodos_sector) * 256;
|
||||
const off = (16 * physical_track + prodos_sector) * 256;
|
||||
sector = new Uint8Array(rawData.slice(off, off + 256));
|
||||
} else {
|
||||
} else if (data) {
|
||||
sector = data[physical_track][prodos_sector];
|
||||
} else {
|
||||
throw new Error('Requires data or rawData');
|
||||
}
|
||||
track = track.concat(
|
||||
explodeSector16(volume, physical_track, physical_sector, sector)
|
174
js/formats/types.ts
Normal file
174
js/formats/types.ts
Normal file
|
@ -0,0 +1,174 @@
|
|||
/* Copyright 2021 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 type { bit, byte, memory, MemberOf } from '../types';
|
||||
import type { GamepadConfiguration } from '../ui/gamepad';
|
||||
|
||||
export const DRIVE_NUMBERS = [1, 2] as const;
|
||||
export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
|
||||
|
||||
/**
|
||||
* Arguments for the disk format processors.
|
||||
*/
|
||||
|
||||
export interface DiskOptions {
|
||||
name: string
|
||||
volume: byte
|
||||
readOnly: boolean
|
||||
data?: memory[][]
|
||||
rawData?: ArrayBuffer
|
||||
blockVolume?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value from disk format processors. Describes raw disk
|
||||
* data which the DiskII card can process.
|
||||
*/
|
||||
|
||||
export interface Disk {
|
||||
format: DiskFormat
|
||||
name: string
|
||||
volume: byte
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
export interface NibbleDisk extends Disk {
|
||||
encoding: 'nibble'
|
||||
tracks: memory[]
|
||||
}
|
||||
|
||||
export interface WozDisk extends Disk {
|
||||
encoding: 'woz'
|
||||
trackMap: number[]
|
||||
rawTracks: bit[][]
|
||||
}
|
||||
|
||||
/**
|
||||
* File types supported by the disk format processors and
|
||||
* block devices.
|
||||
*/
|
||||
|
||||
export const DISK_FORMATS = [
|
||||
'2mg',
|
||||
'd13',
|
||||
'do',
|
||||
'dsk',
|
||||
'hdv',
|
||||
'po',
|
||||
'nib',
|
||||
'woz'
|
||||
] as const;
|
||||
|
||||
export type DiskFormat = MemberOf<typeof DISK_FORMATS>;
|
||||
|
||||
/**
|
||||
* Base format for JSON defined disks
|
||||
*/
|
||||
|
||||
export class JSONDiskBase {
|
||||
type: DiskFormat
|
||||
name: string
|
||||
disk?: number
|
||||
category?: string
|
||||
writeProtected?: boolean
|
||||
volume: byte
|
||||
readOnly: boolean
|
||||
gamepad?: GamepadConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Process Disk message payloads for worker
|
||||
*/
|
||||
|
||||
export const ProcessBinaryType = 'processBinary';
|
||||
export const ProcessJsonDiskType = 'processJsonDisk';
|
||||
export const ProcessJsonType = 'processJson';
|
||||
|
||||
/** Binary disk file message */
|
||||
export interface ProcessBinaryMessage {
|
||||
type: typeof ProcessBinaryType
|
||||
payload: {
|
||||
drive: DriveNumber
|
||||
fmt: DiskFormat
|
||||
options: DiskOptions
|
||||
}
|
||||
}
|
||||
|
||||
/** Processed JSON file message (used for localStorage) */
|
||||
export interface ProcessJsonDiskMessage {
|
||||
type: typeof ProcessJsonDiskType
|
||||
payload: {
|
||||
drive: DriveNumber
|
||||
jsonDisk: JSONDisk
|
||||
}
|
||||
}
|
||||
|
||||
/** Raw JSON file message */
|
||||
export interface ProcessJsonMessage {
|
||||
type: typeof ProcessJsonType
|
||||
payload: {
|
||||
drive: DriveNumber
|
||||
json: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FormatWorkerMessage =
|
||||
ProcessBinaryMessage |
|
||||
ProcessJsonDiskMessage |
|
||||
ProcessJsonMessage;
|
||||
|
||||
/**
|
||||
* Format work result message type
|
||||
*/
|
||||
|
||||
export const DiskProcessedType = 'diskProcessed';
|
||||
|
||||
export interface DiskProcessedResponse {
|
||||
type: typeof DiskProcessedType
|
||||
payload: {
|
||||
drive: DriveNumber
|
||||
disk: Disk | null
|
||||
}
|
||||
}
|
||||
|
||||
export type FormatWorkerResponse =
|
||||
DiskProcessedResponse
|
||||
|
||||
/**
|
||||
* Block device common interface
|
||||
*/
|
||||
export interface MassStorage {
|
||||
setBinary(drive: number, name: string, ext: DiskFormat, data: ArrayBuffer): boolean
|
||||
}
|
56
js/formats/worker.ts
Normal file
56
js/formats/worker.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { debug } from '../util';
|
||||
import { jsonDecode } from './format_utils';
|
||||
import {
|
||||
createDisk,
|
||||
createDiskFromJsonDisk,
|
||||
} from './create_disk';
|
||||
import {
|
||||
FormatWorkerMessage,
|
||||
Disk,
|
||||
DiskProcessedResponse,
|
||||
DiskProcessedType,
|
||||
ProcessBinaryType,
|
||||
ProcessJsonDiskType,
|
||||
ProcessJsonType,
|
||||
} from './types';
|
||||
|
||||
debug('Worker loaded');
|
||||
|
||||
addEventListener('message', (message: MessageEvent<FormatWorkerMessage>) => {
|
||||
debug('Worker started', message.type);
|
||||
const data = message.data;
|
||||
const { drive } = data.payload;
|
||||
let disk: Disk | null = null;
|
||||
|
||||
switch (data.type) {
|
||||
case ProcessBinaryType: {
|
||||
const { fmt, options } = data.payload;
|
||||
disk = createDisk(fmt, options);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProcessJsonDiskType: {
|
||||
const { jsonDisk } = data.payload;
|
||||
disk = createDiskFromJsonDisk(jsonDisk);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProcessJsonType: {
|
||||
const { json } = data.payload;
|
||||
disk = jsonDecode(json);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const response: DiskProcessedResponse = {
|
||||
type: DiskProcessedType,
|
||||
payload: {
|
||||
drive,
|
||||
disk
|
||||
}
|
||||
};
|
||||
|
||||
self.postMessage(response);
|
||||
|
||||
debug('Worker complete', message.type);
|
||||
});
|
36
js/types.ts
36
js/types.ts
|
@ -1,4 +1,3 @@
|
|||
|
||||
/**
|
||||
* Extracts the members of a constant array as a type. Used as:
|
||||
*
|
||||
|
@ -11,7 +10,7 @@ export type MemberOf<T extends ReadonlyArray<unknown>> =
|
|||
|
||||
/**
|
||||
* Recursively extracts all members of a constant array as a type. Used as:
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const SOME_ARRAYS = [['a'],['b', 2], 3] as const;
|
||||
* type SomeArrayValues = DeepMemberOf<typeof SOME_ARRAYS>; // 'a' | 'b' | 2 | 3
|
||||
|
@ -23,7 +22,7 @@ export type DeepMemberOf<T extends ReadonlyArray<unknown>> =
|
|||
|
||||
/**
|
||||
* Extracts the declared keys of a type by removing `string` and `number`.
|
||||
*
|
||||
*
|
||||
* Cribbed from the interwebs:
|
||||
* https://github.com/microsoft/TypeScript/issues/25987#issuecomment-408339599
|
||||
*/
|
||||
|
@ -39,7 +38,7 @@ export type KnownValues<T> = T extends {
|
|||
|
||||
/**
|
||||
* Replacement for `includes` on constant types that is also a type assertion.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const SOME_VALUES = [1, 2, 'a'] as const;
|
||||
* let n: number = 1;
|
||||
|
@ -101,35 +100,6 @@ export interface Card extends Memory, Restorable {
|
|||
ioSwitch(off: byte, val?: byte): byte | undefined;
|
||||
}
|
||||
|
||||
export const DISK_FORMATS = [
|
||||
'2mg',
|
||||
'd13',
|
||||
'do',
|
||||
'dsk',
|
||||
'hdv',
|
||||
'po',
|
||||
'nib',
|
||||
'woz'
|
||||
] as const;
|
||||
|
||||
export type DiskFormat = MemberOf<typeof DISK_FORMATS>;
|
||||
|
||||
export interface Drive {
|
||||
format: DiskFormat,
|
||||
volume: number,
|
||||
tracks: Array<byte[] | Uint8Array>,
|
||||
trackMap: unknown,
|
||||
}
|
||||
|
||||
export interface DiskIIDrive extends Drive {
|
||||
rawTracks: unknown,
|
||||
track: number,
|
||||
head: number,
|
||||
phase: number,
|
||||
readOnly: boolean,
|
||||
dirty: boolean,
|
||||
}
|
||||
|
||||
export type TapeData = Array<[duration: number, high: boolean]>;
|
||||
|
||||
export interface Restorable<T = any> {
|
||||
|
|
|
@ -3,7 +3,14 @@ import MicroModal from 'micromodal';
|
|||
import { base64_json_parse, base64_json_stringify } from '../base64';
|
||||
import { Audio, SOUND_ENABLED_OPTION } from './audio';
|
||||
import DriveLights from './drive_lights';
|
||||
import { byte, DISK_FORMATS, includes, word } from '../types';
|
||||
import { byte, includes, word } from '../types';
|
||||
import { MassStorage } from '../formats/types';
|
||||
import {
|
||||
DISK_FORMATS,
|
||||
DriveNumber,
|
||||
DRIVE_NUMBERS,
|
||||
JSONDisk
|
||||
} from '../formats/types';
|
||||
import { initGamepad, GamepadConfiguration } from './gamepad';
|
||||
import KeyBoard from './keyboard';
|
||||
import Tape, { TAPE_TYPES } from './tape';
|
||||
|
@ -13,12 +20,11 @@ import ApplesoftCompiler from '../applesoft/compiler';
|
|||
|
||||
import { debug, gup, hup } from '../util';
|
||||
import { Apple2, Stats } from '../apple2';
|
||||
import DiskII, { DriveNumber, DRIVE_NUMBERS } from '../cards/disk2';
|
||||
import SmartPort from '../cards/smartport';
|
||||
import DiskII from '../cards/disk2';
|
||||
import CPU6502 from '../cpu6502';
|
||||
import { VideoModes } from '../videomodes';
|
||||
import Apple2IO from '../apple2io';
|
||||
import { JSONDisk } from '../formats/format_utils';
|
||||
import { } from '../formats/format_utils';
|
||||
import Printer from './printer';
|
||||
|
||||
import { OptionsModal } from './options_modal';
|
||||
|
@ -71,7 +77,7 @@ let stats: Stats;
|
|||
let vm: VideoModes;
|
||||
let tape: Tape;
|
||||
let _disk2: DiskII;
|
||||
let _smartPort: SmartPort;
|
||||
let _massStorage: MassStorage;
|
||||
let _printer: Printer;
|
||||
let audio: Audio;
|
||||
let screen: Screen;
|
||||
|
@ -381,8 +387,12 @@ function doLoadLocalDisk(drive: DriveNumber, file: File) {
|
|||
document.location.hash = files.join('|');
|
||||
|
||||
if (result.byteLength >= 800 * 1024) {
|
||||
if (_smartPort.setBinary(drive, name, ext, result)) {
|
||||
initGamepad();
|
||||
if (includes(DISK_FORMATS, ext)) {
|
||||
if (_massStorage.setBinary(drive, name, ext, result)) {
|
||||
initGamepad();
|
||||
} else {
|
||||
openAlert(`Unable to load ${name}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (includes(DISK_FORMATS, ext)
|
||||
|
@ -442,18 +452,18 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
|
|||
const fileParts = file.split('.');
|
||||
const ext = fileParts.pop()!.toLowerCase();
|
||||
const name = decodeURIComponent(fileParts.join('.'));
|
||||
if (data.byteLength >= 800 * 1024) {
|
||||
if (_smartPort.setBinary(drive, name, ext, data)) {
|
||||
initGamepad();
|
||||
}
|
||||
} else {
|
||||
if (includes(DISK_FORMATS, ext)) {
|
||||
if (_disk2.setBinary(drive, name, ext, data)) {
|
||||
if (includes(DISK_FORMATS, ext)) {
|
||||
if (data.byteLength >= 800 * 1024) {
|
||||
if (_massStorage.setBinary(drive, name, ext, data)) {
|
||||
initGamepad();
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Extension ${ext} not recognized.`);
|
||||
if (_disk2.setBinary(drive, name, ext, data)) {
|
||||
initGamepad();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Extension ${ext} not recognized.`);
|
||||
}
|
||||
loadingStop();
|
||||
}).catch(function (error) {
|
||||
|
@ -685,7 +695,6 @@ const categorySelect = document.querySelector<HTMLSelectElement>('#category_sele
|
|||
declare global {
|
||||
interface Window {
|
||||
disk_index: DiskDescriptor[];
|
||||
e: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -808,7 +817,7 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
function onLoaded(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer: Printer, e: boolean) {
|
||||
function onLoaded(apple2: Apple2, disk2: DiskII, massStorage: MassStorage, printer: Printer, e: boolean) {
|
||||
_apple2 = apple2;
|
||||
cpu = _apple2.getCPU();
|
||||
io = _apple2.getIO();
|
||||
|
@ -816,7 +825,7 @@ function onLoaded(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer:
|
|||
vm = apple2.getVideoModes();
|
||||
tape = new Tape(io);
|
||||
_disk2 = disk2;
|
||||
_smartPort = smartPort;
|
||||
_massStorage = massStorage;
|
||||
_printer = printer;
|
||||
_e = e;
|
||||
|
||||
|
@ -905,8 +914,8 @@ function onLoaded(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer:
|
|||
);
|
||||
}
|
||||
|
||||
export function initUI(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer: Printer, e: boolean) {
|
||||
export function initUI(apple2: Apple2, disk2: DiskII, massStorage: MassStorage, printer: Printer, e: boolean) {
|
||||
window.addEventListener('load', () => {
|
||||
onLoaded(apple2, disk2, smartPort, printer, e);
|
||||
onLoaded(apple2, disk2, massStorage, printer, e);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Callbacks, DriveNumber } from '../cards/disk2';
|
||||
import { Callbacks } from '../cards/disk2';
|
||||
import type { DriveNumber } from '../formats/types';
|
||||
|
||||
export default class DriveLights implements Callbacks {
|
||||
public driveLight(drive: DriveNumber, on: boolean) {
|
||||
|
|
|
@ -347,4 +347,4 @@ describe('DOS-13 format', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -284,4 +284,4 @@ describe('DOS format', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -284,4 +284,4 @@ describe('ProDOS format', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
26
test/js/formats/testdata/13sector.ts
vendored
26
test/js/formats/testdata/13sector.ts
vendored
|
@ -1,11 +1,11 @@
|
|||
import { byte } from '../../../../js/types';
|
||||
import { memory } from '../../../../js/types';
|
||||
|
||||
function generateBytesInOrder() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 13; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector: memory = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = b;
|
||||
}
|
||||
|
@ -16,14 +16,14 @@ function generateBytesInOrder() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_IN_ORDER: byte[][][] = generateBytesInOrder();
|
||||
export const BYTES_IN_ORDER: memory[][] = generateBytesInOrder();
|
||||
|
||||
function generateBytesBySector() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 13; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector: memory = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = s;
|
||||
}
|
||||
|
@ -34,14 +34,14 @@ function generateBytesBySector() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_BY_SECTOR: byte[][][] = generateBytesBySector();
|
||||
export const BYTES_BY_SECTOR: memory[][] = generateBytesBySector();
|
||||
|
||||
function generateBytesByTrack() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 13; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector: memory = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = t;
|
||||
}
|
||||
|
@ -52,4 +52,4 @@ function generateBytesByTrack() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_BY_TRACK: byte[][][] = generateBytesByTrack();
|
||||
export const BYTES_BY_TRACK: memory[][] = generateBytesByTrack();
|
||||
|
|
26
test/js/formats/testdata/16sector.ts
vendored
26
test/js/formats/testdata/16sector.ts
vendored
|
@ -1,11 +1,11 @@
|
|||
import { byte } from '../../../../js/types';
|
||||
import { memory } from '../../../../js/types';
|
||||
|
||||
function generateBytesInOrder() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 16; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = b;
|
||||
}
|
||||
|
@ -16,14 +16,14 @@ function generateBytesInOrder() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_IN_ORDER: byte[][][] = generateBytesInOrder();
|
||||
export const BYTES_IN_ORDER: memory[][] = generateBytesInOrder();
|
||||
|
||||
function generateBytesBySector() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 16; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = s;
|
||||
}
|
||||
|
@ -34,14 +34,14 @@ function generateBytesBySector() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_BY_SECTOR: byte[][][] = generateBytesBySector();
|
||||
export const BYTES_BY_SECTOR: memory[][] = generateBytesBySector();
|
||||
|
||||
function generateBytesByTrack() {
|
||||
const data: byte[][][] = [];
|
||||
const data: memory[][] = [];
|
||||
for (let t = 0; t < 35; t++) {
|
||||
const track: byte[][] = [];
|
||||
const track: memory[] = [];
|
||||
for (let s = 0; s < 16; s++) {
|
||||
const sector: byte[] = [];
|
||||
const sector = new Uint8Array(256);
|
||||
for (let b = 0; b < 256; b++) {
|
||||
sector[b] = t;
|
||||
}
|
||||
|
@ -52,4 +52,4 @@ function generateBytesByTrack() {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const BYTES_BY_TRACK: byte[][][] = generateBytesByTrack();
|
||||
export const BYTES_BY_TRACK: memory[][] = generateBytesByTrack();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es6",
|
||||
"lib": ["DOM", "ES2017", "WebWorker"],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
|
|
|
@ -55,7 +55,8 @@ module.exports = [
|
|||
...baseConfig,
|
||||
target: false,
|
||||
entry: {
|
||||
audio_worker: path.resolve('js/ui/audio_worker.ts')
|
||||
audio_worker: path.resolve('js/ui/audio_worker.ts'),
|
||||
format_worker: path.resolve('js/formats/worker.ts')
|
||||
},
|
||||
output: {
|
||||
publicPath: '/dist/',
|
||||
|
|
Loading…
Reference in New Issue
Block a user