TypeScript remnants (#94)
* Clean up remaining Javascript * ProDOS to TypeScript. * NSC to TypeScript
This commit is contained in:
parent
c24c01539d
commit
ba203498f4
|
@ -103,8 +103,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"js/entry2.js",
|
"js/entry2.ts",
|
||||||
"js/entry2e.js",
|
"js/entry2e.ts",
|
||||||
"jest.config.js"
|
"jest.config.js"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
|
|
|
@ -85,7 +85,6 @@ export interface CFFAState {
|
||||||
disks: Array<BlockDisk | null>
|
disks: Array<BlockDisk | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
type Partition = ReturnType<typeof ProDOSVolume>
|
|
||||||
export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
||||||
|
|
||||||
// CFFA internal Flags
|
// CFFA internal Flags
|
||||||
|
@ -123,7 +122,7 @@ export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
||||||
|
|
||||||
// Disk data
|
// Disk data
|
||||||
|
|
||||||
private _partitions: Array<Partition|null> = [
|
private _partitions: Array<ProDOSVolume|null> = [
|
||||||
// Drive 1
|
// Drive 1
|
||||||
null,
|
null,
|
||||||
// Drive 2
|
// 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.SectorCountHigh] = this._sectors[0].length & 0xffff;
|
||||||
this._identity[drive][IDENTITY.SectorCountLow] = this._sectors[0].length >> 16;
|
this._identity[drive][IDENTITY.SectorCountLow] = this._sectors[0].length >> 16;
|
||||||
|
|
||||||
const prodos = ProDOSVolume(disk);
|
const prodos = new ProDOSVolume(disk);
|
||||||
dump(prodos);
|
dump(prodos);
|
||||||
|
|
||||||
this._partitions[drive] = prodos;
|
this._partitions[drive] = prodos;
|
||||||
|
|
115
js/cards/nsc.js
115
js/cards/nsc.js
|
@ -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(_) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { MassStorage, BlockDisk, ENCODING_BLOCK } from '../formats/types';
|
||||||
import CPU6502, { CpuState, flags } from '../cpu6502';
|
import CPU6502, { CpuState, flags } from '../cpu6502';
|
||||||
import { read2MGHeader } from '../formats/2mg';
|
import { read2MGHeader } from '../formats/2mg';
|
||||||
import createBlockDisk from '../formats/block';
|
import createBlockDisk from '../formats/block';
|
||||||
|
import { ProDOSVolume } from '../formats/prodos';
|
||||||
|
import { dump } from '../formats/prodos/utils';
|
||||||
|
|
||||||
export interface SmartPortState {
|
export interface SmartPortState {
|
||||||
disks: BlockDisk[]
|
disks: BlockDisk[]
|
||||||
|
@ -494,6 +496,9 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||||
|
|
||||||
this.disks[drive] = createBlockDisk(options);
|
this.disks[drive] = createBlockDisk(options);
|
||||||
|
|
||||||
|
const prodos = new ProDOSVolume(this.disks[drive]);
|
||||||
|
dump(prodos);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
export var BLOCK_SIZE = 512;
|
export const BLOCK_SIZE = 512;
|
||||||
|
|
||||||
export var STORAGE_TYPES = {
|
export const STORAGE_TYPES = {
|
||||||
DELETED: 0x0,
|
DELETED: 0x0,
|
||||||
SEEDLING: 0x1,
|
SEEDLING: 0x1,
|
||||||
SAPLING: 0x2,
|
SAPLING: 0x2,
|
||||||
|
@ -8,18 +8,18 @@ export var STORAGE_TYPES = {
|
||||||
DIRECTORY: 0xD,
|
DIRECTORY: 0xD,
|
||||||
SUBDIRECTORY_HEADER: 0xE,
|
SUBDIRECTORY_HEADER: 0xE,
|
||||||
VDH_HEADER: 0xF
|
VDH_HEADER: 0xF
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export var ACCESS_TYPES = {
|
export const ACCESS_TYPES = {
|
||||||
DELETE: 0x80,
|
DELETE: 0x80,
|
||||||
RENAME: 0x40,
|
RENAME: 0x40,
|
||||||
BACKUP: 0x20,
|
BACKUP: 0x20,
|
||||||
WRITE: 0x02,
|
WRITE: 0x02,
|
||||||
READ: 0x01,
|
READ: 0x01,
|
||||||
ALL: 0xE3
|
ALL: 0xE3
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export var FILE_TYPES = {
|
export const FILE_TYPES = {
|
||||||
0x00: 'UNK', // Typeless file (SOS and ProDOS)
|
0x00: 'UNK', // Typeless file (SOS and ProDOS)
|
||||||
0x01: 'BAD', // Bad block file
|
0x01: 'BAD', // Bad block file
|
||||||
0x02: 'PDC', // Pascal code file
|
0x02: 'PDC', // Pascal code file
|
||||||
|
@ -51,4 +51,4 @@ export var FILE_TYPES = {
|
||||||
0xFD: 'VAR', // Applesoft variables file
|
0xFD: 'VAR', // Applesoft variables file
|
||||||
0xFE: 'REL', // Relocatable code file (EDASM)
|
0xFE: 'REL', // Relocatable code file (EDASM)
|
||||||
0xFF: 'SYS' // ProDOS system file
|
0xFF: 'SYS' // ProDOS system file
|
||||||
};
|
} as const;
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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, ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -257,6 +257,7 @@ export function loadAjax(drive: DriveNumber, url: string) {
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
loadingStop();
|
loadingStop();
|
||||||
openAlert(error.message);
|
openAlert(error.message);
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,6 +483,7 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
loadingStop();
|
loadingStop();
|
||||||
openAlert(error.message);
|
openAlert(error.message);
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@ const baseConfig = {
|
||||||
const appConfig = merge(baseConfig,
|
const appConfig = merge(baseConfig,
|
||||||
{
|
{
|
||||||
entry: {
|
entry: {
|
||||||
main2: path.resolve('js/entry2.js'),
|
main2: path.resolve('js/entry2.ts'),
|
||||||
main2e: path.resolve('js/entry2e.js')
|
main2e: path.resolve('js/entry2e.ts')
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
library: {
|
library: {
|
||||||
|
|
Loading…
Reference in New Issue