TypeScript remnants (#94)

* Clean up remaining Javascript
* ProDOS to TypeScript.
* NSC to TypeScript
This commit is contained in:
Will Scullin 2021-12-22 10:37:21 -08:00 committed by GitHub
parent c24c01539d
commit ba203498f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1064 additions and 1022 deletions

View File

@ -103,8 +103,8 @@
},
{
"files": [
"js/entry2.js",
"js/entry2e.js",
"js/entry2.ts",
"js/entry2e.ts",
"jest.config.js"
],
"env": {

View File

@ -85,7 +85,6 @@ export interface CFFAState {
disks: Array<BlockDisk | null>
}
type Partition = ReturnType<typeof ProDOSVolume>
export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
// CFFA internal Flags
@ -123,7 +122,7 @@ export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
// Disk data
private _partitions: Array<Partition|null> = [
private _partitions: Array<ProDOSVolume|null> = [
// Drive 1
null,
// Drive 2
@ -435,7 +434,7 @@ export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
this._identity[drive][IDENTITY.SectorCountHigh] = this._sectors[0].length & 0xffff;
this._identity[drive][IDENTITY.SectorCountLow] = this._sectors[0].length >> 16;
const prodos = ProDOSVolume(disk);
const prodos = new ProDOSVolume(disk);
dump(prodos);
this._partitions[drive] = prodos;

View File

@ -1,115 +0,0 @@
import { debug } from '../util';
export default function NoSlotClock(rom)
{
var PATTERN = [
0xC5, 0x3A, 0xA3, 0x5C, 0xC5, 0x3A, 0xA3, 0x5C
];
var A0 = 0x01;
var A2 = 0x04;
function _init() {
debug('NoSlotClock');
}
var _bits = [];
var _pattern = new Array(64);
var _patternIdx = 0;
function _patternMatch() {
for (var idx = 0; idx < 8; idx++) {
var byte = 0;
for (var jdx = 0; jdx < 8; jdx++) {
byte >>= 1;
byte |= _pattern.shift() ? 0x80 : 0x00;
}
if (byte !== PATTERN[idx]) {
return false;
}
}
return true;
}
function _calcBits() {
function shift(val) {
for (var idx = 0; idx < 4; idx++) {
_bits.push(val & 0x08 ? 0x01 : 0x00);
val <<= 1;
}
}
function shiftBCD(val) {
shift(parseInt(val / 10, 10));
shift(parseInt(val % 10, 10));
}
var now = new Date();
var year = now.getFullYear() % 100;
var day = now.getDate();
var weekday = now.getDay() + 1;
var month = now.getMonth() + 1;
var hour = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();
var hundredths = (now.getMilliseconds() / 10);
_bits = [];
shiftBCD(year);
shiftBCD(month);
shiftBCD(day);
shiftBCD(weekday);
shiftBCD(hour);
shiftBCD(minutes);
shiftBCD(seconds);
shiftBCD(hundredths);
}
_init();
function _access(off) {
if (off & A2) {
_patternIdx = 0;
} else {
var bit = off & A0;
_pattern[_patternIdx++] = bit;
if (_patternIdx === 64) {
if (_patternMatch()) {
_calcBits();
}
_patternIdx = 0;
}
}
}
return {
start: function nsc_start() {
return rom.start();
},
end: function nsc_end() {
return rom.end();
},
read: function nsc_read(page, off) {
if (_bits.length > 0) {
var bit = _bits.pop();
return bit;
} else {
_access(off);
}
return rom.read(page, off);
},
write: function nsc_write(page, off, val) {
_access(off);
rom.write(page, off, val);
},
getState() {
return {};
},
setState(_) {
}
};
}

115
js/cards/nsc.ts Normal file
View File

@ -0,0 +1,115 @@
import ROM from 'js/roms/rom';
import { bit, byte } from 'js/types';
import { debug } from '../util';
const PATTERN = [
0xC5, 0x3A, 0xA3, 0x5C, 0xC5, 0x3A, 0xA3, 0x5C
];
const A0 = 0x01;
const A2 = 0x04;
export default class NoSlotClock {
bits: bit[] = [];
pattern = new Array(64);
patternIdx: number = 0;
constructor(private rom: ROM) {
debug('NoSlotClock');
}
private patternMatch() {
for (let idx = 0; idx < 8; idx++) {
let byte = 0;
for (let jdx = 0; jdx < 8; jdx++) {
byte >>= 1;
byte |= this.pattern.shift() ? 0x80 : 0x00;
}
if (byte !== PATTERN[idx]) {
return false;
}
}
return true;
}
private calcBits() {
const shift = (val: byte) => {
for (let idx = 0; idx < 4; idx++) {
this.bits.push(val & 0x08 ? 0x01 : 0x00);
val <<= 1;
}
};
const shiftBCD = (val: byte) => {
shift(Math.floor(val / 10));
shift(Math.floor(val % 10));
};
const now = new Date();
const year = now.getFullYear() % 100;
const day = now.getDate();
const weekday = now.getDay() + 1;
const month = now.getMonth() + 1;
const hour = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
const hundredths = (now.getMilliseconds() / 10);
this.bits = [];
shiftBCD(year);
shiftBCD(month);
shiftBCD(day);
shiftBCD(weekday);
shiftBCD(hour);
shiftBCD(minutes);
shiftBCD(seconds);
shiftBCD(hundredths);
}
access(off: byte) {
if (off & A2) {
this.patternIdx = 0;
} else {
const bit = off & A0;
this.pattern[this.patternIdx++] = bit;
if (this.patternIdx === 64) {
if (this.patternMatch()) {
this.calcBits();
}
this.patternIdx = 0;
}
}
}
start() {
return this.rom.start();
}
end() {
return this.rom.end();
}
read(page: byte, off: byte) {
if (this.bits.length > 0) {
const bit = this.bits.pop();
return bit;
} else {
this.access(off);
}
return this.rom.read(page, off);
}
write(_page: byte, off: byte, _val: byte) {
this.access(off);
this.rom.write();
}
getState() {
return {};
}
setState(_: unknown) {
}
}

View File

@ -5,6 +5,8 @@ import { MassStorage, BlockDisk, ENCODING_BLOCK } from '../formats/types';
import CPU6502, { CpuState, flags } from '../cpu6502';
import { read2MGHeader } from '../formats/2mg';
import createBlockDisk from '../formats/block';
import { ProDOSVolume } from '../formats/prodos';
import { dump } from '../formats/prodos/utils';
export interface SmartPortState {
disks: BlockDisk[]
@ -494,6 +496,9 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
this.disks[drive] = createBlockDisk(options);
const prodos = new ProDOSVolume(this.disks[drive]);
dump(prodos);
return true;
}
}

View File

@ -1,46 +0,0 @@
export function BitMap(volume) {
var vdh = volume.vdh();
var blocks = volume.blocks();
var BLOCK_ENTRIES = 4096;
function _init() {
}
_init();
return {
allocBlock: function () {
for (var idx = 0; idx < vdh.totalBlocks; idx++) {
var blockOffset = vdh.bitMapPointer + Math.floor(idx / BLOCK_ENTRIES);
var bitMapBlock = blocks[blockOffset];
var byteOffset = (idx - blockOffset * BLOCK_ENTRIES) >> 8;
var bits = bitMapBlock[byteOffset];
if (bits !== 0xff) {
var mask = 0x01;
for (var bitOffset = 0; bitOffset < 8; bitOffset++) {
if (!(bits & mask)) {
bitMapBlock[byteOffset] |= mask;
return idx;
}
mask <<= 1;
}
}
}
throw new Error('Disk full');
},
freeBlock: function (block) {
if (block >= vdh.totalBlocks) {
throw new Error('Block out of range');
}
var blockOffset = vdh.bitMapPointer + Math.floor(block / BLOCK_ENTRIES);
var byteOffset = (block - blockOffset * BLOCK_ENTRIES) >> 8;
var bitOffset = block & 0x7;
var bitMapBlock = blocks[blockOffset];
bitMapBlock[byteOffset] &= 0xff ^ (0x01 << bitOffset);
}
};
}

View File

@ -0,0 +1,49 @@
import { word } from 'js/types';
import { ProDOSVolume } from '.';
import type { VDH } from './vdh';
const BLOCK_ENTRIES = 4096;
export class BitMap {
private vdh: VDH;
private blocks: Uint8Array[];
constructor(volume: ProDOSVolume) {
this.vdh = volume.vdh();
this.blocks = volume.blocks();
}
allocBlock () {
for (let idx = 0; idx < this.vdh.totalBlocks; idx++) {
const blockOffset = this.vdh.bitMapPointer + Math.floor(idx / BLOCK_ENTRIES);
const bitMapBlock = this.blocks[blockOffset];
const byteOffset = (idx - blockOffset * BLOCK_ENTRIES) >> 8;
const bits = bitMapBlock[byteOffset];
if (bits !== 0xff) {
let mask = 0x01;
for (let bitOffset = 0; bitOffset < 8; bitOffset++) {
if (!(bits & mask)) {
bitMapBlock[byteOffset] |= mask;
return idx;
}
mask <<= 1;
}
}
}
throw new Error('Disk full');
}
freeBlock(block: word) {
if (block >= this.vdh.totalBlocks) {
throw new Error('Block out of range');
}
const blockOffset = this.vdh.bitMapPointer + Math.floor(block / BLOCK_ENTRIES);
const byteOffset = (block - blockOffset * BLOCK_ENTRIES) >> 8;
const bitOffset = block & 0x7;
const bitMapBlock = this.blocks[blockOffset];
bitMapBlock[byteOffset] &= 0xff ^ (0x01 << bitOffset);
}
}

View File

@ -1,6 +1,6 @@
export var BLOCK_SIZE = 512;
export const BLOCK_SIZE = 512;
export var STORAGE_TYPES = {
export const STORAGE_TYPES = {
DELETED: 0x0,
SEEDLING: 0x1,
SAPLING: 0x2,
@ -8,18 +8,18 @@ export var STORAGE_TYPES = {
DIRECTORY: 0xD,
SUBDIRECTORY_HEADER: 0xE,
VDH_HEADER: 0xF
};
} as const;
export var ACCESS_TYPES = {
export const ACCESS_TYPES = {
DELETE: 0x80,
RENAME: 0x40,
BACKUP: 0x20,
WRITE: 0x02,
READ: 0x01,
ALL: 0xE3
};
} as const;
export var FILE_TYPES = {
export const FILE_TYPES = {
0x00: 'UNK', // Typeless file (SOS and ProDOS)
0x01: 'BAD', // Bad block file
0x02: 'PDC', // Pascal code file
@ -51,4 +51,4 @@ export var FILE_TYPES = {
0xFD: 'VAR', // Applesoft variables file
0xFE: 'REL', // Relocatable code file (EDASM)
0xFF: 'SYS' // ProDOS system file
};
} as const;

View File

@ -1,87 +0,0 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function Directory(volume, fileEntry) {
var _fileEntry = fileEntry;
var DIRECTORY_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
VOLUME_NAME: 0x05,
RESERVED_1: 0x14,
CREATION: 0x1C,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
PARENT: 0x27,
PARENT_ENTRY_NUMBER: 0x29,
PARENT_ENTRY_LENGTH: 0x2A
};
var blocks = volume.blocks();
return {
prev: 0,
next: 0,
storageType: STORAGE_TYPES.DIRECTORY,
name: 'Untitled',
creation: new Date(),
access: ACCESS_TYPES.ALL,
entryLength: 0x27,
entriesPerBlock: 23,
fileCount: 0,
parent: 0,
parentEntryLength: 0,
parentEntryNumber: 0,
entries: [],
read: function(fileEntry) {
fileEntry = fileEntry || _fileEntry;
var block = new DataView(blocks[fileEntry.keyPointer].buffer);
this.prev = block.getUint16(DIRECTORY_OFFSETS.PREV, true);
this.next = block.getUint16(DIRECTORY_OFFSETS.NEXT, true);
this.storageType = block.getUint8(DIRECTORY_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(DIRECTORY_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint8(DIRECTORY_OFFSETS.CASE_BITS);
this.name = readFileName(block, DIRECTORY_OFFSETS.VOLUME_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(DIRECTORY_OFFSETS.CREATION, true));
this.access = block.getUint8(DIRECTORY_OFFSETS.ACCESS);
this.entryLength = block.getUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(DIRECTORY_OFFSETS.FILE_COUNT, true);
this.parent = block.getUint16(DIRECTORY_OFFSETS.PARENT, true);
this.parentEntryNumber = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER);
this.parentEntryLength = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH);
this.entries = readEntries(volume, block, this);
},
write: function() {
var block = new DataView(blocks[fileEntry.keyPointer].buffer);
var nameLength = name.length & 0x0f;
block.setUint8(DIRECTORY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, DIRECTORY_OFFSETS.FILE_NAME, this.name);
block.setUint32(DIRECTORY_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(DIRECTORY_OFFSETS.CASE_BITS, caseBits);
block.setUint8(DIRECTORY_OFFSETS.ACCESS, this.access);
block.setUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(DIRECTORY_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(DIRECTORY_OFFSETS.PARENT, this.parent, true);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER, this.parentEntryNumber);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH, this.parentEntryLength);
writeEntries(volume, block, this);
}
};
}

View File

@ -0,0 +1,93 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { FileEntry, readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
import { byte, word } from 'js/types';
import { ProDOSVolume } from '.';
import { VDH } from './vdh';
export const DIRECTORY_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
DIRECTORY_NAME: 0x05,
RESERVED_1: 0x14,
CREATION: 0x1C,
CASE_BITS: 0x20,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
PARENT: 0x27,
PARENT_ENTRY_NUMBER: 0x29,
PARENT_ENTRY_LENGTH: 0x2A
} as const;
export class Directory {
blocks: Uint8Array[];
vdh: VDH;
prev: word = 0;
next: word = 0;
storageType: byte = STORAGE_TYPES.DIRECTORY;
name: string = 'Untitled';
creation: Date = new Date();
access: byte = ACCESS_TYPES.ALL;
entryLength = 0x27;
entriesPerBlock: byte = 23;
fileCount = 0;
parent: word = 0;
parentEntryLength: byte = 0;
parentEntryNumber: byte = 0;
entries: FileEntry[] = [];
constructor(private volume: ProDOSVolume, private fileEntry: FileEntry) {
this.blocks = this.volume.blocks();
this.vdh = this.volume.vdh();
}
read(fileEntry?: FileEntry) {
this.fileEntry = fileEntry ?? this.fileEntry;
const block = new DataView(this.blocks[this.fileEntry.keyPointer].buffer);
this.prev = block.getUint16(DIRECTORY_OFFSETS.PREV, true);
this.next = block.getUint16(DIRECTORY_OFFSETS.NEXT, true);
this.storageType = block.getUint8(DIRECTORY_OFFSETS.STORAGE_TYPE) >> 4;
const nameLength = block.getUint8(DIRECTORY_OFFSETS.NAME_LENGTH) & 0xF;
const caseBits = block.getUint8(DIRECTORY_OFFSETS.CASE_BITS);
this.name = readFileName(block, DIRECTORY_OFFSETS.DIRECTORY_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(DIRECTORY_OFFSETS.CREATION, true));
this.access = block.getUint8(DIRECTORY_OFFSETS.ACCESS);
this.entryLength = block.getUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(DIRECTORY_OFFSETS.FILE_COUNT, true);
this.parent = block.getUint16(DIRECTORY_OFFSETS.PARENT, true);
this.parentEntryNumber = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER);
this.parentEntryLength = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH);
this.entries = readEntries(this.volume, block, this);
}
write() {
const block = new DataView(this.blocks[this.fileEntry.keyPointer].buffer);
const nameLength = this.name.length & 0x0f;
block.setUint8(DIRECTORY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
const caseBits = writeFileName(block, DIRECTORY_OFFSETS.DIRECTORY_NAME, this.name);
block.setUint32(DIRECTORY_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(DIRECTORY_OFFSETS.CASE_BITS, caseBits);
block.setUint8(DIRECTORY_OFFSETS.ACCESS, this.access);
block.setUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(DIRECTORY_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(DIRECTORY_OFFSETS.PARENT, this.parent, true);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER, this.parentEntryNumber);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH, this.parentEntryLength);
writeEntries(this.volume, block, this.vdh);
}
}

View File

@ -1,139 +0,0 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function FileEntry () {
var ENTRY_OFFSETS = {
STORAGE_TYPE: 0x00,
NAME_LENGTH: 0x00,
FILE_NAME: 0x01,
FILE_TYPE: 0x10,
KEY_POINTER: 0x11,
BLOCKS_USED: 0x13,
EOF: 0x15,
CREATION: 0x18,
CASE_BITS: 0x1C,
VERSION: 0x1C,
MIN_VERSION: 0x1D,
ACCESS: 0x1E,
AUX_TYPE: 0x1F,
LAST_MOD: 0x21,
HEADER_POINTER: 0x25
};
var _block;
var _offset;
return {
storageType: STORAGE_TYPES.SEEDLING,
name: 'Untitled',
fileType: 0,
auxType: 0,
blocksUsed: 0,
eof: 0,
access: ACCESS_TYPES.ALL,
creation: new Date(),
lastMod: new Date(),
keyPointer: 0,
headerPointer: 0,
read: function(block, offset) {
_block = block;
_offset = offset;
this.storageType = block.getUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(offset + ENTRY_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint16(offset + ENTRY_OFFSETS.CASE_BITS, true);
this.name = readFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, nameLength, caseBits);
this.fileType = block.getUint8(offset + ENTRY_OFFSETS.FILE_TYPE);
this.keyPointer = block.getUint16(offset + ENTRY_OFFSETS.KEY_POINTER, true);
this.blocksUsed = block.getUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, true);
this.eof =
block.getUint8(offset + ENTRY_OFFSETS.EOF) |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 1) << 8 |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 2) << 16;
this.creation = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.CREATION, true));
this.access = block.getUint8(offset + ENTRY_OFFSETS.ACCESS);
this.auxType = block.getUint16(offset + ENTRY_OFFSETS.AUX_TYPE, true);
this.lastMod = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.LAST_MOD, true));
this.headerPointer = block.getUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, true);
},
write: function(block, offset) {
block = block || _block;
offset = offset || _offset;
var nameLength = name.length & 0x0f;
block.setUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, this.name);
block.setUint16(offset + ENTRY_OFFSETS.CASE_BITS, caseBits);
block.setUint8(offset + ENTRY_OFFSETS.FILE_TYPE, this.fileType);
block.setUint16(offset + ENTRY_OFFSETS.KEY_POINTER, this.keyPointer, true);
block.setUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, this.blocksUsed, true);
block.setUint8(offset + ENTRY_OFFSETS.EOF, this.eof & 0xff);
block.setUint8(offset + ENTRY_OFFSETS.EOF + 1, (this.eof && 0xff00) >> 8);
block.setUint8(offset + ENTRY_OFFSETS.EOF + 2, this.eof >> 16);
block.setUint32(offset + ENTRY_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint8(offset + ENTRY_OFFSETS.ACCESS, this.access);
block.setUint16(offset + ENTRY_OFFSETS.AUX_TYPE, this.auxType, true);
block.setUint32(offset + ENTRY_OFFSETS.LAST_MOD, dateToUint32(this.lastMod), true);
block.setUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, this.headerPointer, true);
}
};
}
export function readEntries(volume, block, header) {
var blocks = volume.blocks();
var entries = [];
var offset = header.entryLength + 0x4;
var count = 2;
var next = header.next;
for (var idx = 0; idx < header.fileCount; idx++) {
var fileEntry = new FileEntry();
fileEntry.read(block, offset);
entries.push(fileEntry);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
block = new DataView(blocks[next].buffer);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
return entries;
}
export function writeEntries(volume, block, header) {
var blocks = volume.blocks();
var bitMap = volume.bitmap();
var offset = header.entryLength + 0x4;
var count = 2;
var next = header.next;
for (var idx = 0; idx < header.fileCount; idx++) {
var fileEntry = new header.entries[idx];
fileEntry.write(block, offset);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
var prev = next;
if (!next) {
next = bitMap.allocBlock();
}
block = new DataView(blocks[next].buffer);
block.setUint16(0x00, prev, true);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
next = block.getUint16(0x02, true);
block.setUint16(0x02, 0, true);
while (next) {
block = new DataView(blocks[next].buffer);
bitMap.freeLock(next);
next = block.getUint16(0x02, true);
}
}

View File

@ -0,0 +1,141 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
import type { byte, word } from 'js/types';
import { ProDOSVolume } from '.';
import { VDH } from './vdh';
import { Directory } from './directory';
const ENTRY_OFFSETS = {
STORAGE_TYPE: 0x00,
NAME_LENGTH: 0x00,
FILE_NAME: 0x01,
FILE_TYPE: 0x10,
KEY_POINTER: 0x11,
BLOCKS_USED: 0x13,
EOF: 0x15,
CREATION: 0x18,
CASE_BITS: 0x1C,
VERSION: 0x1C,
MIN_VERSION: 0x1D,
ACCESS: 0x1E,
AUX_TYPE: 0x1F,
LAST_MOD: 0x21,
HEADER_POINTER: 0x25
} as const;
export class FileEntry {
block: DataView;
offset: word;
storageType: byte = STORAGE_TYPES.SEEDLING;
name: string = 'Untitled';
fileType: byte = 0;
auxType: word = 0;
blocksUsed: word = 0;
eof: number = 0;
access: byte = ACCESS_TYPES.ALL;
creation: Date = new Date();
lastMod: Date = new Date();
keyPointer: word = 0;
headerPointer: word = 0;
read(block: DataView, offset: word) {
this.block = block;
this.offset = offset;
this.storageType = block.getUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE) >> 4;
const nameLength = block.getUint8(offset + ENTRY_OFFSETS.NAME_LENGTH) & 0xF;
const caseBits = block.getUint16(offset + ENTRY_OFFSETS.CASE_BITS, true);
this.name = readFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, nameLength, caseBits);
this.fileType = block.getUint8(offset + ENTRY_OFFSETS.FILE_TYPE);
this.keyPointer = block.getUint16(offset + ENTRY_OFFSETS.KEY_POINTER, true);
this.blocksUsed = block.getUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, true);
this.eof =
block.getUint8(offset + ENTRY_OFFSETS.EOF) |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 1) << 8 |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 2) << 16;
this.creation = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.CREATION, true));
this.access = block.getUint8(offset + ENTRY_OFFSETS.ACCESS);
this.auxType = block.getUint16(offset + ENTRY_OFFSETS.AUX_TYPE, true);
this.lastMod = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.LAST_MOD, true));
this.headerPointer = block.getUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, true);
}
write(block?: DataView, offset?: word) {
this.block = block ?? this.block;
this.offset = offset ?? this.offset;
const nameLength = this.name.length & 0x0f;
this.block.setUint8(this.offset + ENTRY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
const caseBits = writeFileName(this.block, this.offset + ENTRY_OFFSETS.FILE_NAME, this.name);
this.block.setUint16(this.offset + ENTRY_OFFSETS.CASE_BITS, caseBits);
this.block.setUint8(this.offset + ENTRY_OFFSETS.FILE_TYPE, this.fileType);
this.block.setUint16(this.offset + ENTRY_OFFSETS.KEY_POINTER, this.keyPointer, true);
this.block.setUint16(this.offset + ENTRY_OFFSETS.BLOCKS_USED, this.blocksUsed, true);
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF, this.eof & 0xff);
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF + 1, (this.eof && 0xff00) >> 8);
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF + 2, this.eof >> 16);
this.block.setUint32(this.offset + ENTRY_OFFSETS.CREATION, dateToUint32(this.creation), true);
this.block.setUint8(this.offset + ENTRY_OFFSETS.ACCESS, this.access);
this.block.setUint16(this.offset + ENTRY_OFFSETS.AUX_TYPE, this.auxType, true);
this.block.setUint32(this.offset + ENTRY_OFFSETS.LAST_MOD, dateToUint32(this.lastMod), true);
this.block.setUint16(this.offset + ENTRY_OFFSETS.HEADER_POINTER, this.headerPointer, true);
}
}
export function readEntries(volume: ProDOSVolume, block: DataView, header: VDH | Directory) {
const blocks = volume.blocks();
const entries = [];
let offset = header.entryLength + 0x4;
let count = 2;
let next = header.next;
for (let idx = 0; idx < header.fileCount; idx++) {
const fileEntry = new FileEntry();
fileEntry.read(block, offset);
entries.push(fileEntry);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
block = new DataView(blocks[next].buffer);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
return entries;
}
export function writeEntries(volume: ProDOSVolume, block: DataView, header: VDH | Directory) {
const blocks = volume.blocks();
const bitMap = volume.bitMap();
let offset = header.entryLength + 0x4;
let count = 2;
let next = header.next;
for (let idx = 0; idx < header.fileCount; idx++) {
const fileEntry = header.entries[idx];
fileEntry.write(block, offset);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
const prev = next;
if (!next) {
next = bitMap.allocBlock();
}
block = new DataView(blocks[next].buffer);
block.setUint16(0x00, prev, true);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
next = block.getUint16(0x02, true);
block.setUint16(0x02, 0, true);
while (next) {
block = new DataView(blocks[next].buffer);
bitMap.freeBlock(next);
next = block.getUint16(0x02, true);
}
}

View File

@ -1,33 +0,0 @@
import { VDH } from './vdh';
import { BitMap } from './bit_map';
export function ProDOSVolume(disk) {
var _disk = disk;
var _vdh;
var _bitMap;
return {
disk() {
return _disk;
},
blocks() {
return _disk.blocks;
},
vdh() {
if (!_vdh) {
_vdh = new VDH(this);
_vdh.read();
}
return _vdh;
},
bitMap() {
if (!_bitMap) {
_bitMap = new BitMap(this);
}
return _bitMap;
}
};
}

View File

@ -0,0 +1,33 @@
import { VDH } from './vdh';
import { BitMap } from './bit_map';
import { BlockDisk } from '../types';
export class ProDOSVolume {
_vdh: VDH;
_bitMap: BitMap;
constructor(private _disk: BlockDisk) {}
disk() {
return this._disk;
}
blocks() {
return this._disk.blocks;
}
vdh() {
if (!this._vdh) {
this._vdh = new VDH(this);
this._vdh.read();
}
return this._vdh;
}
bitMap() {
if (!this._bitMap) {
this._bitMap = new BitMap(this);
}
return this._bitMap;
}
}

View File

@ -1,76 +0,0 @@
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
export function SaplingFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var pointers = [fileEntry.keyPointer];
for (var idx = 0; idx < 256; idx++) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
return pointers;
},
read: function() {
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var remainingLength = fileEntry.oef;
var data = new Uint8Array(remainingLength);
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
var seedlingBlock = blocks[seedlingPointer];
var bytes = seedlingBlock.slice(0, Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
return data;
},
write: function(data) {
fileEntry.storageType = STORAGE_TYPES.SAPLING;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var remainingLength = data.byteLength;
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var seedlingPointer = bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
var seedlingBlock = blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

View File

@ -0,0 +1,83 @@
import { ProDOSVolume } from '.';
import type { BitMap } from './bit_map';
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
import { FileEntry } from './file_entry';
export class SaplingFile {
blocks: Uint8Array[];
bitMap: BitMap;
constructor(private volume: ProDOSVolume, private fileEntry: FileEntry) {
this.blocks = this.volume.blocks();
this.bitMap = this.volume.bitMap();
}
getBlockPointers() {
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
const seedlingPointers = new DataView(saplingBlock);
const pointers = [this.fileEntry.keyPointer];
for (let idx = 0; idx < 256; idx++) {
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
return pointers;
}
read() {
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
const seedlingPointers = new DataView(saplingBlock);
let remainingLength = this.fileEntry.eof;
const data = new Uint8Array(remainingLength);
let offset = 0;
let idx = 0;
while (remainingLength > 0) {
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
const seedlingBlock = this.blocks[seedlingPointer];
const bytes = seedlingBlock.slice(0, Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
return data;
}
write(data: Uint8Array) {
this.fileEntry.storageType = STORAGE_TYPES.SAPLING;
this.fileEntry.keyPointer = this.bitMap.allocBlock();
this.fileEntry.eof = data.byteLength;
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
const seedlingPointers = new DataView(saplingBlock);
let remainingLength = data.byteLength;
let offset = 0;
let idx = 0;
while (remainingLength > 0) {
const seedlingPointer = this.bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
const seedlingBlock = this.blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
this.fileEntry.write();
}
delete() {
const pointers = this.getBlockPointers();
for (let idx = 0; idx < pointers.length; idx++) {
this.bitMap.freeBlock(pointers[idx]);
}
}
}

View File

@ -1,40 +0,0 @@
import { STORAGE_TYPES } from './constants';
export function SeedlingFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var pointers = [fileEntry.keyPointer];
return pointers;
},
read: function () {
var seedlingBlock = blocks[fileEntry.keyPointer];
var data = new Uint8Array(fileEntry.eof);
data.set(seedlingBlock.slice(0, fileEntry.eof));
return data;
},
write: function(data) {
if (fileEntry.keyPointer) {
this.delete();
}
fileEntry.storageType = STORAGE_TYPES.SEEDLING;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var seedlingBlock = blocks[fileEntry.keyPointer];
seedlingBlock.set(data);
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

View File

@ -0,0 +1,46 @@
import type { ProDOSVolume } from '.';
import { BitMap } from './bit_map';
import { STORAGE_TYPES } from './constants';
import { FileEntry } from './file_entry';
export class SeedlingFile {
blocks: Uint8Array[];
bitMap: BitMap;
constructor(volume: ProDOSVolume, private fileEntry: FileEntry) {
this.blocks = volume.blocks();
this.bitMap = volume.bitMap();
}
getBlockPointers() {
const pointers = [this.fileEntry.keyPointer];
return pointers;
}
read() {
const seedlingBlock = this.blocks[this.fileEntry.keyPointer];
const data = new Uint8Array(this.fileEntry.eof);
data.set(seedlingBlock.slice(0, this.fileEntry.eof));
return data;
}
write(data: Uint8Array) {
if (this.fileEntry.keyPointer) {
this.delete();
}
this.fileEntry.storageType = STORAGE_TYPES.SEEDLING;
this.fileEntry.keyPointer = this.bitMap.allocBlock();
this.fileEntry.eof = data.byteLength;
const seedlingBlock = this.blocks[this.fileEntry.keyPointer];
seedlingBlock.set(data);
this.fileEntry.write();
}
delete() {
const pointers = this.getBlockPointers();
for (let idx = 0; idx < pointers.length; idx++) {
this.bitMap.freeBlock(pointers[idx]);
}
}
}

View File

@ -1,105 +0,0 @@
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
export function TreeFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var pointers = [];
for (var idx = 0; idx < 256; idx++) {
var saplingPointer = saplingPointers.getUint16(idx * 2);
if (saplingPointer) {
pointers.push(saplingPointer);
var seedlingPointers = new DataView(blocks[saplingPointer]);
for (var jdx = 0; jdx < 256; jdx++) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
}
}
return pointers;
},
read: function() {
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var remainingLength = fileEntry.eof;
var data = new Uint8Array(remainingLength);
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var saplingPointer = saplingPointers.getUint16(idx * 2, true);
var jdx = 0;
if (saplingPointer) {
var saplingBlock = blocks[saplingPointer];
var seedlingPointers = new DataView(saplingBlock);
while (jdx < 256 && remainingLength > 0) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2, true);
if (seedlingPointer) {
var seedlingBlock = blocks[seedlingPointer];
var bytes = seedlingBlock.slice(Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
} else {
offset += BLOCK_SIZE * 256;
remainingLength -= BLOCK_SIZE * 256;
}
idx++;
}
return data;
},
write: function(data) {
fileEntry.storageType = STORAGE_TYPES.TREE;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var remainingLength = fileEntry.eof;
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var saplingPointer = bitMap.allocBlock();
var saplingBlock = blocks[saplingPointer];
saplingPointers.setUint16(idx * 2, saplingPointer, true);
var seedlingPointers = new DataView(saplingBlock);
var jdx = 0;
while (jdx < 256 && remainingLength > 0) {
var seedlingPointer = bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
var seedlingBlock = blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
idx++;
}
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

View File

@ -0,0 +1,112 @@
import type { ProDOSVolume } from '.';
import { BitMap } from './bit_map';
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
import type { FileEntry } from './file_entry';
export class TreeFile {
private bitMap: BitMap;
private blocks: Uint8Array[];
constructor (volume: ProDOSVolume, private fileEntry: FileEntry) {
this.blocks = volume.blocks();
this.bitMap = volume.bitMap();
}
getBlockPointers() {
const treeBlock = this.blocks[this.fileEntry.keyPointer];
const saplingPointers = new DataView(treeBlock);
const pointers = [];
for (let idx = 0; idx < 256; idx++) {
const saplingPointer = saplingPointers.getUint16(idx * 2);
if (saplingPointer) {
pointers.push(saplingPointer);
const seedlingPointers = new DataView(this.blocks[saplingPointer]);
for (let jdx = 0; jdx < 256; jdx++) {
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
}
}
return pointers;
}
read() {
const treeBlock = this.blocks[this.fileEntry.keyPointer];
const saplingPointers = new DataView(treeBlock);
let remainingLength = this.fileEntry.eof;
const data = new Uint8Array(remainingLength);
let offset = 0;
let idx = 0;
while (remainingLength > 0) {
const saplingPointer = saplingPointers.getUint16(idx * 2, true);
let jdx = 0;
if (saplingPointer) {
const saplingBlock = this.blocks[saplingPointer];
const seedlingPointers = new DataView(saplingBlock);
while (jdx < 256 && remainingLength > 0) {
const seedlingPointer = seedlingPointers.getUint16(idx * 2, true);
if (seedlingPointer) {
const seedlingBlock = this.blocks[seedlingPointer];
const bytes = seedlingBlock.slice(Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
} else {
offset += BLOCK_SIZE * 256;
remainingLength -= BLOCK_SIZE * 256;
}
idx++;
}
return data;
}
write(data: Uint8Array) {
this.fileEntry.storageType = STORAGE_TYPES.TREE;
this.fileEntry.keyPointer = this.bitMap.allocBlock();
this.fileEntry.eof = data.byteLength;
const treeBlock = this.blocks[this.fileEntry.keyPointer];
const saplingPointers = new DataView(treeBlock);
let remainingLength = this.fileEntry.eof;
let offset = 0;
let idx = 0;
while (remainingLength > 0) {
const saplingPointer = this.bitMap.allocBlock();
const saplingBlock = this.blocks[saplingPointer];
saplingPointers.setUint16(idx * 2, saplingPointer, true);
const seedlingPointers = new DataView(saplingBlock);
let jdx = 0;
while (jdx < 256 && remainingLength > 0) {
const seedlingPointer = this.bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
const seedlingBlock = this.blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
idx++;
}
this.fileEntry.write();
}
delete() {
const pointers = this.getBlockPointers();
for (let idx = 0; idx < pointers.length; idx++) {
this.bitMap.freeBlock(pointers[idx]);
}
}
}

View File

View File

@ -1,97 +0,0 @@
import { debug } from '../../util';
import { STORAGE_TYPES } from './constants';
import { Directory } from './directory';
export function uint32ToDate(val) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
if (val) {
var yearMonthDay = val & 0xffff;
var hourMinute = val >> 16;
var year = yearMonthDay >> 9;
var month = (yearMonthDay & 0x01E0) >> 5;
var day = yearMonthDay & 0x001F;
var hour = hourMinute >> 8;
var min = hourMinute & 0xff;
return new Date(1900 + year, month - 1, day, hour, min);
}
return null;
}
export function dateToUint32(date) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
var val = 0;
if (date) {
var year = date.getYear() - 1900;
var month = date.getMonth() + 1;
var day = date.getDate();
var hour = date.getHour();
var min = date.getMinute();
var yearMonthDay = year << 9 | month << 5 | day;
var hourMinute = hour << 8 | min;
val = hourMinute << 16 | yearMonthDay;
}
return val;
}
export function readFileName(block, offset, nameLength, caseBits) {
var name = '';
if (!(caseBits & 0x8000)) {
caseBits = 0;
}
for (var idx = 0; idx < nameLength; idx++) {
caseBits <<= 1;
var char = String.fromCharCode(block.getUint8(offset + idx));
name += caseBits & 0x8000 ? char.toLowerCase() : char;
}
return name;
}
export function writeFileName(block, offset, name) {
var caseBits = 0;
for (var idx = 0; idx < name.length; idx++) {
caseBits <<= 1;
var charCode = name.charCodeAt(idx);
if (charCode > 0x60 && charCode < 0x7B) {
caseBits |= 0x1;
charCode -= 0x20;
}
block.setUint8(offset + idx, charCode);
}
return caseBits;
}
export function dumpDirectory(volume, dirEntry, depth) {
var dir = new Directory(volume, dirEntry);
dir.read();
for (var idx = 0; idx < dir.entries.length; idx++) {
var fileEntry = dir.entries[idx];
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
debug(depth, fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, depth + ' ');
}
}
}
}
export function dump(volume) {
var vdh = volume.vdh();
debug(vdh.name);
for (var idx = 0; idx < vdh.entries.length; idx++) {
var fileEntry = vdh.entries[idx];
debug(fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, ' ');
}
}
}

100
js/formats/prodos/utils.ts Normal file
View File

@ -0,0 +1,100 @@
import { debug } from '../../util';
import { STORAGE_TYPES } from './constants';
import { Directory } from './directory';
import type { byte, word } from 'js/types';
import { ProDOSVolume } from '.';
import { FileEntry } from './file_entry';
export function uint32ToDate(val: word) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
if (val) {
const yearMonthDay = val & 0xffff;
const hourMinute = val >> 16;
const year = yearMonthDay >> 9;
const month = (yearMonthDay & 0x01E0) >> 5;
const day = yearMonthDay & 0x001F;
const hour = hourMinute >> 8;
const min = hourMinute & 0xff;
return new Date(1900 + year, month - 1, day, hour, min);
}
return new Date(0);
}
export function dateToUint32(date: Date) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
let val = 0;
if (date) {
const year = date.getFullYear() - 1900;
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const min = date.getMinutes();
const yearMonthDay = year << 9 | month << 5 | day;
const hourMinute = hour << 8 | min;
val = hourMinute << 16 | yearMonthDay;
}
return val;
}
export function readFileName(block: DataView, offset: word, nameLength: byte, caseBits: word) {
let name = '';
if (!(caseBits & 0x8000)) {
caseBits = 0;
}
for (let idx = 0; idx < nameLength; idx++) {
caseBits <<= 1;
const char = String.fromCharCode(block.getUint8(offset + idx));
name += caseBits & 0x8000 ? char.toLowerCase() : char;
}
return name;
}
export function writeFileName(block: DataView, offset: word, name: string) {
let caseBits = 0;
for (let idx = 0; idx < name.length; idx++) {
caseBits <<= 1;
let charCode = name.charCodeAt(idx);
if (charCode > 0x60 && charCode < 0x7B) {
caseBits |= 0x1;
charCode -= 0x20;
}
block.setUint8(offset + idx, charCode);
}
return caseBits;
}
export function dumpDirectory(volume: ProDOSVolume, dirEntry: FileEntry, depth: string) {
const dir = new Directory(volume, dirEntry);
dir.read();
for (let idx = 0; idx < dir.entries.length; idx++) {
const fileEntry = dir.entries[idx];
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
debug(depth, fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, depth + ' ');
}
}
}
}
export function dump(volume: ProDOSVolume) {
const vdh = volume.vdh();
debug(vdh.name);
for (let idx = 0; idx < vdh.entries.length; idx++) {
const fileEntry = vdh.entries[idx];
debug(fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, ' ');
}
}
}

View File

@ -1,80 +0,0 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function VDH(volume) {
var blocks = volume.blocks();
var VDH_BLOCK = 2;
var VDH_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
VOLUME_NAME: 0x05,
RESERVED_1: 0x14,
CASE_BITS: 0x1A,
CREATION: 0x1C,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
BIT_MAP_POINTER: 0x27,
TOTAL_BLOCKS: 0x29,
};
return {
prev: 0,
next: 0,
storageType: STORAGE_TYPES.VDH_HEADER,
name: '',
creation: new Date(),
access: ACCESS_TYPES.ALL,
entryLength: 0x27,
entriesPerBlock: 23,
fileCount: 0,
bitMapPointer: 0,
totalBlocks: 0,
entries: [],
read: function () {
var block = new DataView(blocks[VDH_BLOCK].buffer);
this.next = block.getUint16(VDH_OFFSETS.NEXT, true);
this.storageType = block.getUint8(VDH_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(VDH_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint8(VDH_OFFSETS.CASE_BITS);
this.name = readFileName(block, VDH_OFFSETS.VOLUME_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(VDH_OFFSETS.CREATION, true));
this.access = block.getUint8(VDH_OFFSETS.ACCESS);
this.entryLength = block.getUint8(VDH_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(VDH_OFFSETS.FILE_COUNT, true);
this.bitMapPointer = block.getUint16(VDH_OFFSETS.BIT_MAP_POINTER, true);
this.totalBlocks = block.getUint16(VDH_OFFSETS.TOTAL_BLOCKS, true);
this.entries = readEntries(volume, block, this);
},
write: function() {
var block = new DataView(blocks[VDH_BLOCK].buffer);
var nameLength = name.length & 0x0f;
block.setUint8(VDH_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, VDH_OFFSETS.FILE_NAME, this.name);
block.setUint32(VDH_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(VDH_OFFSETS.CASE_BITS, caseBits);
block.setUint8(VDH_OFFSETS.ACCESS, this.access);
block.setUint8(VDH_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(VDH_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(VDH_OFFSETS.BIT_MAP_POINTER, this.bitMapPointer, true);
block.setUint16(VDH_OFFSETS.TOTAL_BLOCKS, this.totalBlocks, true);
writeEntries(blocks, block, this);
}
};
}

84
js/formats/prodos/vdh.ts Normal file
View File

@ -0,0 +1,84 @@
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { FileEntry, readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
import { byte, word } from 'js/types';
import { ProDOSVolume } from '.';
const VDH_BLOCK = 2;
const VDH_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
VOLUME_NAME: 0x05,
RESERVED_1: 0x14,
CASE_BITS: 0x1A,
CREATION: 0x1C,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
BIT_MAP_POINTER: 0x27,
TOTAL_BLOCKS: 0x29,
} as const;
export class VDH {
private blocks: Uint8Array[];
prev: word = 0;
next: word = 0;
storageType: byte = STORAGE_TYPES.VDH_HEADER;
name: string = '';
creation: Date = new Date();
access: byte = ACCESS_TYPES.ALL;
entryLength = 0x27;
entriesPerBlock = 23;
fileCount = 0;
bitMapPointer: word = 0;
totalBlocks: word = 0;
entries: FileEntry[] = [];
constructor(private volume: ProDOSVolume) {
this.blocks = this.volume.blocks();
}
read() {
const block = new DataView(this.blocks[VDH_BLOCK].buffer);
this.next = block.getUint16(VDH_OFFSETS.NEXT, true);
this.storageType = block.getUint8(VDH_OFFSETS.STORAGE_TYPE) >> 4;
const nameLength = block.getUint8(VDH_OFFSETS.NAME_LENGTH) & 0xF;
const caseBits = block.getUint8(VDH_OFFSETS.CASE_BITS);
this.name = readFileName(block, VDH_OFFSETS.VOLUME_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(VDH_OFFSETS.CREATION, true));
this.access = block.getUint8(VDH_OFFSETS.ACCESS);
this.entryLength = block.getUint8(VDH_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(VDH_OFFSETS.FILE_COUNT, true);
this.bitMapPointer = block.getUint16(VDH_OFFSETS.BIT_MAP_POINTER, true);
this.totalBlocks = block.getUint16(VDH_OFFSETS.TOTAL_BLOCKS, true);
this.entries = readEntries(this.volume, block, this);
}
write() {
const block = new DataView(this.blocks[VDH_BLOCK].buffer);
const nameLength = this.name.length & 0x0f;
block.setUint8(VDH_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
const caseBits = writeFileName(block, VDH_OFFSETS.VOLUME_NAME, this.name);
block.setUint32(VDH_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(VDH_OFFSETS.CASE_BITS, caseBits);
block.setUint8(VDH_OFFSETS.ACCESS, this.access);
block.setUint8(VDH_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(VDH_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(VDH_OFFSETS.BIT_MAP_POINTER, this.bitMapPointer, true);
block.setUint16(VDH_OFFSETS.TOTAL_BLOCKS, this.totalBlocks, true);
writeEntries(this.volume, block, this);
}
}

View File

@ -1,190 +0,0 @@
export default function IntBasicDump(mem)
{
var _mem = mem;
var LETTERS =
' ' +
' !"#$%&\'()*+,-./0123456789:;<=>?' +
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_' +
'`abcdefghijklmnopqrstuvwxyz{|}~ ';
var TOKENS = {
0x02: '_',
0x03: ':',
0x04: 'LOAD',
0x05: 'SAVE',
0x06: 'CON',
0x07: 'RUN',
0x08: 'RUN',
0x09: 'DEL',
0x0A: ',',
0x0B: 'NEW',
0x0C: 'CLR',
0x0D: 'AUTO',
0x0E: ',',
0x0F: 'MAN',
0x10: 'HIMEM:',
0x11: 'LOMEM:',
0x12: '+',
0x13: '-',
0x14: '*',
0x15: '/',
0x16: '=',
0x17: '#',
0x18: '>=',
0x19: '>',
0x1A: '<=',
0x1B: '<>',
0x1C: '<',
0x1D: 'AND',
0x1E: 'OR',
0x1F: 'MOD',
0x20: '^',
0x21: '+',
0x22: '(',
0x23: ',',
0x24: 'THEN',
0x25: 'THEN',
0x26: ',',
0x27: ',',
0x28: '\'',
0x29: '\'',
0x2A: '(',
0x2B: '!',
0x2C: '!',
0x2D: '(',
0x2E: 'PEEK',
0x2F: 'RND',
0x30: 'SGN',
0x31: 'ABS',
0x32: 'PDL',
0x33: 'RNDX',
0x34: '(',
0x35: '+',
0x36: '-',
0x37: 'NOT',
0x38: '(',
0x39: '=',
0x3A: '#',
0x3B: 'LEN(',
0x3C: 'ASC(',
0x3D: 'SCRN(',
0x3E: ',',
0x3F: '(',
0x40: '$',
0x41: '$',
0x42: '(',
0x43: ',',
0x44: ',',
0x45: ';',
0x46: ';',
0x47: ';',
0x48: ',',
0x49: ',',
0x4A: ',',
0x4B: 'TEXT',
0x4C: 'GR',
0x4D: 'CALL',
0x4E: 'DIM',
0x4F: 'DIM',
0x50: 'TAB',
0x51: 'END',
0x52: 'INPUT',
0x53: 'INPUT',
0x54: 'INPUT',
0x55: 'FOR',
0x56: '=',
0x57: 'TO',
0x58: 'STEP',
0x59: 'NEXT',
0x5A: ',',
0x5B: 'RETURN',
0x5C: 'GOSUB',
0x5D: 'REM',
0x5E: 'LET',
0x5F: 'GOTO',
0x60: 'IF',
0x61: 'PRINT',
0x62: 'PRINT',
0x63: 'PRINT',
0x64: 'POKE',
0x65: ',',
0x66: 'COLOR=',
0x67: 'PLOT',
0x68: ',',
0x69: 'HLIN',
0x6A: ',',
0x6B: 'AT',
0x6C: 'VLIN',
0x6E: 'AT',
0x6F: 'VTAB',
0x70: '=',
0x71: '=',
0x72: ')',
0x73: ')',
0x74: 'LIST',
0x76: 'LIST',
0x77: 'POP',
0x78: 'NODSP',
0x79: 'NODSP',
0x7A: 'NOTRACE',
0x7B: 'DSP',
0x7C: 'DSP',
0x7D: 'TRACE',
0x7E: 'PR#',
0x7F: 'IN#'
};
function readByte(addr) {
var page = addr >> 8,
off = addr & 0xff;
return _mem.read(page, off);
}
function readWord(addr) {
var lsb, msb;
lsb = readByte(addr);
msb = readByte(addr + 1);
return (msb << 8) | lsb;
}
return {
toString: function () {
var str = '';
var addr = readWord(0xca); // Start
var himem = readWord(0x4c);
do {
/*var len = */readByte(addr++);
var lineno = readWord(addr);
addr += 2;
str += lineno;
str += ' ';
var val = 0;
do {
val = readByte(addr++);
if (val >= 0xB0 && val <= 0xB9) {
str += readWord(addr);
addr += 2;
}
else if (val < 0x80 && val > 0x01) {
var t = TOKENS[val];
if (t.length > 1)
str += ' ';
str += t;
if (t.length > 1)
str += ' ';
}
else if (val > 0x80)
str += LETTERS[val - 0x80];
} while (val != 0x01);
str += '\n';
} while (addr < himem);
return str;
}
};
}

188
js/intbasic/decompiler.ts Normal file
View File

@ -0,0 +1,188 @@
import { byte, Memory, word } from 'js/types';
const LETTERS =
' ' +
' !"#$%&\'()*+,-./0123456789:;<=>?' +
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_' +
'`abcdefghijklmnopqrstuvwxyz{|}~ ';
const TOKENS: Record<byte, string> = {
0x02: '_',
0x03: ':',
0x04: 'LOAD',
0x05: 'SAVE',
0x06: 'CON',
0x07: 'RUN',
0x08: 'RUN',
0x09: 'DEL',
0x0A: ',',
0x0B: 'NEW',
0x0C: 'CLR',
0x0D: 'AUTO',
0x0E: ',',
0x0F: 'MAN',
0x10: 'HIMEM:',
0x11: 'LOMEM:',
0x12: '+',
0x13: '-',
0x14: '*',
0x15: '/',
0x16: '=',
0x17: '#',
0x18: '>=',
0x19: '>',
0x1A: '<=',
0x1B: '<>',
0x1C: '<',
0x1D: 'AND',
0x1E: 'OR',
0x1F: 'MOD',
0x20: '^',
0x21: '+',
0x22: '(',
0x23: ',',
0x24: 'THEN',
0x25: 'THEN',
0x26: ',',
0x27: ',',
0x28: '\'',
0x29: '\'',
0x2A: '(',
0x2B: '!',
0x2C: '!',
0x2D: '(',
0x2E: 'PEEK',
0x2F: 'RND',
0x30: 'SGN',
0x31: 'ABS',
0x32: 'PDL',
0x33: 'RNDX',
0x34: '(',
0x35: '+',
0x36: '-',
0x37: 'NOT',
0x38: '(',
0x39: '=',
0x3A: '#',
0x3B: 'LEN(',
0x3C: 'ASC(',
0x3D: 'SCRN(',
0x3E: ',',
0x3F: '(',
0x40: '$',
0x41: '$',
0x42: '(',
0x43: ',',
0x44: ',',
0x45: ';',
0x46: ';',
0x47: ';',
0x48: ',',
0x49: ',',
0x4A: ',',
0x4B: 'TEXT',
0x4C: 'GR',
0x4D: 'CALL',
0x4E: 'DIM',
0x4F: 'DIM',
0x50: 'TAB',
0x51: 'END',
0x52: 'INPUT',
0x53: 'INPUT',
0x54: 'INPUT',
0x55: 'FOR',
0x56: '=',
0x57: 'TO',
0x58: 'STEP',
0x59: 'NEXT',
0x5A: ',',
0x5B: 'RETURN',
0x5C: 'GOSUB',
0x5D: 'REM',
0x5E: 'LET',
0x5F: 'GOTO',
0x60: 'IF',
0x61: 'PRINT',
0x62: 'PRINT',
0x63: 'PRINT',
0x64: 'POKE',
0x65: ',',
0x66: 'COLOR=',
0x67: 'PLOT',
0x68: ',',
0x69: 'HLIN',
0x6A: ',',
0x6B: 'AT',
0x6C: 'VLIN',
0x6E: 'AT',
0x6F: 'VTAB',
0x70: '=',
0x71: '=',
0x72: ')',
0x73: ')',
0x74: 'LIST',
0x76: 'LIST',
0x77: 'POP',
0x78: 'NODSP',
0x79: 'NODSP',
0x7A: 'NOTRACE',
0x7B: 'DSP',
0x7C: 'DSP',
0x7D: 'TRACE',
0x7E: 'PR#',
0x7F: 'IN#'
};
export default class IntBasicDump
{
constructor(private mem: Memory) {}
private readByte(addr: word) {
const page = addr >> 8,
off = addr & 0xff;
return this.mem.read(page, off);
}
private readWord(addr: word) {
const lsb = this.readByte(addr);
const msb = this.readByte(addr + 1);
return (msb << 8) | lsb;
}
toString () {
let str = '';
let addr = this.readWord(0xca); // Start
const himem = this.readWord(0x4c);
do {
/*var len = */this.readByte(addr++);
const lineno = this.readWord(addr);
addr += 2;
str += lineno;
str += ' ';
let val = 0;
do {
val = this.readByte(addr++);
if (val >= 0xB0 && val <= 0xB9) {
str += this.readWord(addr);
addr += 2;
}
else if (val < 0x80 && val > 0x01) {
const t = TOKENS[val];
if (t.length > 1)
str += ' ';
str += t;
if (t.length > 1)
str += ' ';
}
else if (val > 0x80)
str += LETTERS[val - 0x80];
} while (val != 0x01);
str += '\n';
} while (addr < himem);
return str;
}
}

View File

@ -257,6 +257,7 @@ export function loadAjax(drive: DriveNumber, url: string) {
}).catch(function (error) {
loadingStop();
openAlert(error.message);
console.error(error);
});
}
@ -482,6 +483,7 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
}).catch(function (error) {
loadingStop();
openAlert(error.message);
console.error(error);
});
}
}

View File

@ -31,8 +31,8 @@ const baseConfig = {
const appConfig = merge(baseConfig,
{
entry: {
main2: path.resolve('js/entry2.js'),
main2e: path.resolve('js/entry2e.js')
main2: path.resolve('js/entry2.ts'),
main2e: path.resolve('js/entry2e.ts')
},
output: {
library: {