diff --git a/.eslintrc.json b/.eslintrc.json index f615dee..cbbbcc8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -103,8 +103,8 @@ }, { "files": [ - "js/entry2.js", - "js/entry2e.js", + "js/entry2.ts", + "js/entry2e.ts", "jest.config.js" ], "env": { diff --git a/js/cards/cffa.ts b/js/cards/cffa.ts index 72ff7a4..be18880 100644 --- a/js/cards/cffa.ts +++ b/js/cards/cffa.ts @@ -85,7 +85,6 @@ export interface CFFAState { disks: Array } -type Partition = ReturnType export default class CFFA implements Card, MassStorage, Restorable { // CFFA internal Flags @@ -123,7 +122,7 @@ export default class CFFA implements Card, MassStorage, Restorable { // Disk data - private _partitions: Array = [ + private _partitions: Array = [ // Drive 1 null, // Drive 2 @@ -435,7 +434,7 @@ export default class CFFA implements Card, MassStorage, Restorable { 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; diff --git a/js/cards/nsc.js b/js/cards/nsc.js deleted file mode 100644 index 0b820e8..0000000 --- a/js/cards/nsc.js +++ /dev/null @@ -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(_) { - } - }; -} diff --git a/js/cards/nsc.ts b/js/cards/nsc.ts new file mode 100644 index 0000000..fc1cb73 --- /dev/null +++ b/js/cards/nsc.ts @@ -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) { + } +} + diff --git a/js/cards/smartport.ts b/js/cards/smartport.ts index 6c67a95..36b82c1 100644 --- a/js/cards/smartport.ts +++ b/js/cards/smartport.ts @@ -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> 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); - } - }; -} diff --git a/js/formats/prodos/bit_map.ts b/js/formats/prodos/bit_map.ts new file mode 100644 index 0000000..536f7ad --- /dev/null +++ b/js/formats/prodos/bit_map.ts @@ -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); + } +} diff --git a/js/formats/prodos/constants.js b/js/formats/prodos/constants.ts similarity index 91% rename from js/formats/prodos/constants.js rename to js/formats/prodos/constants.ts index 0a21a5c..651628d 100644 --- a/js/formats/prodos/constants.js +++ b/js/formats/prodos/constants.ts @@ -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; diff --git a/js/formats/prodos/directory.js b/js/formats/prodos/directory.js deleted file mode 100644 index ac98b07..0000000 --- a/js/formats/prodos/directory.js +++ /dev/null @@ -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); - } - }; -} diff --git a/js/formats/prodos/directory.ts b/js/formats/prodos/directory.ts new file mode 100644 index 0000000..bd45f0a --- /dev/null +++ b/js/formats/prodos/directory.ts @@ -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); + } +} diff --git a/js/formats/prodos/file_entry.js b/js/formats/prodos/file_entry.js deleted file mode 100644 index 59a6673..0000000 --- a/js/formats/prodos/file_entry.js +++ /dev/null @@ -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); - } -} diff --git a/js/formats/prodos/file_entry.ts b/js/formats/prodos/file_entry.ts new file mode 100644 index 0000000..c768e2d --- /dev/null +++ b/js/formats/prodos/file_entry.ts @@ -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); + } +} diff --git a/js/formats/prodos/index.js b/js/formats/prodos/index.js deleted file mode 100644 index 298ab6b..0000000 --- a/js/formats/prodos/index.js +++ /dev/null @@ -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; - } - }; -} diff --git a/js/formats/prodos/index.ts b/js/formats/prodos/index.ts new file mode 100644 index 0000000..f06911a --- /dev/null +++ b/js/formats/prodos/index.ts @@ -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; + } +} diff --git a/js/formats/prodos/sapling_file.js b/js/formats/prodos/sapling_file.js deleted file mode 100644 index a81b712..0000000 --- a/js/formats/prodos/sapling_file.js +++ /dev/null @@ -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]); - } - } - }; -} - diff --git a/js/formats/prodos/sapling_file.ts b/js/formats/prodos/sapling_file.ts new file mode 100644 index 0000000..e1fefbc --- /dev/null +++ b/js/formats/prodos/sapling_file.ts @@ -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]); + } + } +} + diff --git a/js/formats/prodos/seedling_file.js b/js/formats/prodos/seedling_file.js deleted file mode 100644 index 13b3d1d..0000000 --- a/js/formats/prodos/seedling_file.js +++ /dev/null @@ -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]); - } - } - }; -} - diff --git a/js/formats/prodos/seedling_file.ts b/js/formats/prodos/seedling_file.ts new file mode 100644 index 0000000..167e10b --- /dev/null +++ b/js/formats/prodos/seedling_file.ts @@ -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]); + } + } +} + diff --git a/js/formats/prodos/tree_file.js b/js/formats/prodos/tree_file.js deleted file mode 100644 index 62c8e74..0000000 --- a/js/formats/prodos/tree_file.js +++ /dev/null @@ -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]); - } - } - }; -} diff --git a/js/formats/prodos/tree_file.ts b/js/formats/prodos/tree_file.ts new file mode 100644 index 0000000..6011a69 --- /dev/null +++ b/js/formats/prodos/tree_file.ts @@ -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]); + } + } +} + diff --git a/js/formats/prodos/types.ts b/js/formats/prodos/types.ts new file mode 100644 index 0000000..e69de29 diff --git a/js/formats/prodos/utils.js b/js/formats/prodos/utils.js deleted file mode 100644 index 387511d..0000000 --- a/js/formats/prodos/utils.js +++ /dev/null @@ -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, ' '); - } - } -} diff --git a/js/formats/prodos/utils.ts b/js/formats/prodos/utils.ts new file mode 100644 index 0000000..57facc6 --- /dev/null +++ b/js/formats/prodos/utils.ts @@ -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, ' '); + } + } +} diff --git a/js/formats/prodos/vdh.js b/js/formats/prodos/vdh.js deleted file mode 100644 index 62bb1b9..0000000 --- a/js/formats/prodos/vdh.js +++ /dev/null @@ -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); - } - - }; -} diff --git a/js/formats/prodos/vdh.ts b/js/formats/prodos/vdh.ts new file mode 100644 index 0000000..d53776c --- /dev/null +++ b/js/formats/prodos/vdh.ts @@ -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); + } +} diff --git a/js/intbasic/decompiler.js b/js/intbasic/decompiler.js deleted file mode 100644 index ccbb188..0000000 --- a/js/intbasic/decompiler.js +++ /dev/null @@ -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; - } - }; -} diff --git a/js/intbasic/decompiler.ts b/js/intbasic/decompiler.ts new file mode 100644 index 0000000..60958fa --- /dev/null +++ b/js/intbasic/decompiler.ts @@ -0,0 +1,188 @@ +import { byte, Memory, word } from 'js/types'; + +const LETTERS = +' ' + +' !"#$%&\'()*+,-./0123456789:;<=>?' + +'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_' + +'`abcdefghijklmnopqrstuvwxyz{|}~ '; + +const TOKENS: Record = { + 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; + } +} diff --git a/js/ui/apple2.ts b/js/ui/apple2.ts index 4fb3988..900c17b 100644 --- a/js/ui/apple2.ts +++ b/js/ui/apple2.ts @@ -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); }); } } diff --git a/webpack.config.js b/webpack.config.js index b123c54..740adca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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: {