mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
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": [
|
||||
"js/entry2.js",
|
||||
"js/entry2e.js",
|
||||
"js/entry2.ts",
|
||||
"js/entry2e.ts",
|
||||
"jest.config.js"
|
||||
],
|
||||
"env": {
|
||||
|
@ -85,7 +85,6 @@ export interface CFFAState {
|
||||
disks: Array<BlockDisk | null>
|
||||
}
|
||||
|
||||
type Partition = ReturnType<typeof ProDOSVolume>
|
||||
export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
||||
|
||||
// CFFA internal Flags
|
||||
@ -123,7 +122,7 @@ export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
||||
|
||||
// Disk data
|
||||
|
||||
private _partitions: Array<Partition|null> = [
|
||||
private _partitions: Array<ProDOSVolume|null> = [
|
||||
// Drive 1
|
||||
null,
|
||||
// Drive 2
|
||||
@ -435,7 +434,7 @@ export default class CFFA implements Card, MassStorage, Restorable<CFFAState> {
|
||||
this._identity[drive][IDENTITY.SectorCountHigh] = this._sectors[0].length & 0xffff;
|
||||
this._identity[drive][IDENTITY.SectorCountLow] = this._sectors[0].length >> 16;
|
||||
|
||||
const prodos = ProDOSVolume(disk);
|
||||
const prodos = new ProDOSVolume(disk);
|
||||
dump(prodos);
|
||||
|
||||
this._partitions[drive] = prodos;
|
||||
|
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(_) {
|
||||
}
|
||||
};
|
||||
}
|
115
js/cards/nsc.ts
Normal file
115
js/cards/nsc.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import ROM from 'js/roms/rom';
|
||||
import { bit, byte } from 'js/types';
|
||||
import { debug } from '../util';
|
||||
|
||||
const PATTERN = [
|
||||
0xC5, 0x3A, 0xA3, 0x5C, 0xC5, 0x3A, 0xA3, 0x5C
|
||||
];
|
||||
|
||||
const A0 = 0x01;
|
||||
const A2 = 0x04;
|
||||
|
||||
export default class NoSlotClock {
|
||||
bits: bit[] = [];
|
||||
pattern = new Array(64);
|
||||
patternIdx: number = 0;
|
||||
|
||||
constructor(private rom: ROM) {
|
||||
debug('NoSlotClock');
|
||||
}
|
||||
|
||||
|
||||
private patternMatch() {
|
||||
for (let idx = 0; idx < 8; idx++) {
|
||||
let byte = 0;
|
||||
for (let jdx = 0; jdx < 8; jdx++) {
|
||||
byte >>= 1;
|
||||
byte |= this.pattern.shift() ? 0x80 : 0x00;
|
||||
}
|
||||
if (byte !== PATTERN[idx]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private calcBits() {
|
||||
const shift = (val: byte) => {
|
||||
for (let idx = 0; idx < 4; idx++) {
|
||||
this.bits.push(val & 0x08 ? 0x01 : 0x00);
|
||||
val <<= 1;
|
||||
}
|
||||
};
|
||||
const shiftBCD = (val: byte) => {
|
||||
shift(Math.floor(val / 10));
|
||||
shift(Math.floor(val % 10));
|
||||
};
|
||||
|
||||
const now = new Date();
|
||||
const year = now.getFullYear() % 100;
|
||||
const day = now.getDate();
|
||||
const weekday = now.getDay() + 1;
|
||||
const month = now.getMonth() + 1;
|
||||
const hour = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
const hundredths = (now.getMilliseconds() / 10);
|
||||
|
||||
this.bits = [];
|
||||
|
||||
shiftBCD(year);
|
||||
shiftBCD(month);
|
||||
shiftBCD(day);
|
||||
shiftBCD(weekday);
|
||||
shiftBCD(hour);
|
||||
shiftBCD(minutes);
|
||||
shiftBCD(seconds);
|
||||
shiftBCD(hundredths);
|
||||
}
|
||||
|
||||
access(off: byte) {
|
||||
if (off & A2) {
|
||||
this.patternIdx = 0;
|
||||
} else {
|
||||
const bit = off & A0;
|
||||
this.pattern[this.patternIdx++] = bit;
|
||||
if (this.patternIdx === 64) {
|
||||
if (this.patternMatch()) {
|
||||
this.calcBits();
|
||||
}
|
||||
this.patternIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.rom.start();
|
||||
}
|
||||
|
||||
end() {
|
||||
return this.rom.end();
|
||||
}
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
if (this.bits.length > 0) {
|
||||
const bit = this.bits.pop();
|
||||
return bit;
|
||||
} else {
|
||||
this.access(off);
|
||||
}
|
||||
return this.rom.read(page, off);
|
||||
}
|
||||
|
||||
write(_page: byte, off: byte, _val: byte) {
|
||||
this.access(off);
|
||||
this.rom.write();
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
setState(_: unknown) {
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { MassStorage, BlockDisk, ENCODING_BLOCK } from '../formats/types';
|
||||
import CPU6502, { CpuState, flags } from '../cpu6502';
|
||||
import { read2MGHeader } from '../formats/2mg';
|
||||
import createBlockDisk from '../formats/block';
|
||||
import { ProDOSVolume } from '../formats/prodos';
|
||||
import { dump } from '../formats/prodos/utils';
|
||||
|
||||
export interface SmartPortState {
|
||||
disks: BlockDisk[]
|
||||
@ -494,6 +496,9 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
|
||||
this.disks[drive] = createBlockDisk(options);
|
||||
|
||||
const prodos = new ProDOSVolume(this.disks[drive]);
|
||||
dump(prodos);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
49
js/formats/prodos/bit_map.ts
Normal file
49
js/formats/prodos/bit_map.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { word } from 'js/types';
|
||||
import { ProDOSVolume } from '.';
|
||||
import type { VDH } from './vdh';
|
||||
|
||||
const BLOCK_ENTRIES = 4096;
|
||||
|
||||
export class BitMap {
|
||||
private vdh: VDH;
|
||||
private blocks: Uint8Array[];
|
||||
|
||||
constructor(volume: ProDOSVolume) {
|
||||
this.vdh = volume.vdh();
|
||||
this.blocks = volume.blocks();
|
||||
}
|
||||
|
||||
|
||||
allocBlock () {
|
||||
for (let idx = 0; idx < this.vdh.totalBlocks; idx++) {
|
||||
const blockOffset = this.vdh.bitMapPointer + Math.floor(idx / BLOCK_ENTRIES);
|
||||
const bitMapBlock = this.blocks[blockOffset];
|
||||
const byteOffset = (idx - blockOffset * BLOCK_ENTRIES) >> 8;
|
||||
const bits = bitMapBlock[byteOffset];
|
||||
if (bits !== 0xff) {
|
||||
let mask = 0x01;
|
||||
for (let bitOffset = 0; bitOffset < 8; bitOffset++) {
|
||||
if (!(bits & mask)) {
|
||||
bitMapBlock[byteOffset] |= mask;
|
||||
return idx;
|
||||
}
|
||||
mask <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error('Disk full');
|
||||
}
|
||||
|
||||
freeBlock(block: word) {
|
||||
if (block >= this.vdh.totalBlocks) {
|
||||
throw new Error('Block out of range');
|
||||
}
|
||||
const blockOffset = this.vdh.bitMapPointer + Math.floor(block / BLOCK_ENTRIES);
|
||||
const byteOffset = (block - blockOffset * BLOCK_ENTRIES) >> 8;
|
||||
const bitOffset = block & 0x7;
|
||||
|
||||
const bitMapBlock = this.blocks[blockOffset];
|
||||
|
||||
bitMapBlock[byteOffset] &= 0xff ^ (0x01 << bitOffset);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
93
js/formats/prodos/directory.ts
Normal file
93
js/formats/prodos/directory.ts
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
|
||||
import { FileEntry, readEntries, writeEntries } from './file_entry';
|
||||
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
|
||||
import { byte, word } from 'js/types';
|
||||
import { ProDOSVolume } from '.';
|
||||
import { VDH } from './vdh';
|
||||
|
||||
export const DIRECTORY_OFFSETS = {
|
||||
PREV: 0x00,
|
||||
NEXT: 0x02,
|
||||
STORAGE_TYPE: 0x04,
|
||||
NAME_LENGTH: 0x04,
|
||||
DIRECTORY_NAME: 0x05,
|
||||
RESERVED_1: 0x14,
|
||||
CREATION: 0x1C,
|
||||
CASE_BITS: 0x20,
|
||||
VERSION: 0x20,
|
||||
MIN_VERSION: 0x21,
|
||||
ACCESS: 0x22,
|
||||
ENTRY_LENGTH: 0x23,
|
||||
ENTRIES_PER_BLOCK: 0x24,
|
||||
FILE_COUNT: 0x25,
|
||||
PARENT: 0x27,
|
||||
PARENT_ENTRY_NUMBER: 0x29,
|
||||
PARENT_ENTRY_LENGTH: 0x2A
|
||||
} as const;
|
||||
|
||||
export class Directory {
|
||||
blocks: Uint8Array[];
|
||||
vdh: VDH;
|
||||
|
||||
prev: word = 0;
|
||||
next: word = 0;
|
||||
storageType: byte = STORAGE_TYPES.DIRECTORY;
|
||||
name: string = 'Untitled';
|
||||
creation: Date = new Date();
|
||||
access: byte = ACCESS_TYPES.ALL;
|
||||
entryLength = 0x27;
|
||||
entriesPerBlock: byte = 23;
|
||||
fileCount = 0;
|
||||
parent: word = 0;
|
||||
parentEntryLength: byte = 0;
|
||||
parentEntryNumber: byte = 0;
|
||||
entries: FileEntry[] = [];
|
||||
|
||||
constructor(private volume: ProDOSVolume, private fileEntry: FileEntry) {
|
||||
this.blocks = this.volume.blocks();
|
||||
this.vdh = this.volume.vdh();
|
||||
}
|
||||
|
||||
read(fileEntry?: FileEntry) {
|
||||
this.fileEntry = fileEntry ?? this.fileEntry;
|
||||
|
||||
const block = new DataView(this.blocks[this.fileEntry.keyPointer].buffer);
|
||||
|
||||
this.prev = block.getUint16(DIRECTORY_OFFSETS.PREV, true);
|
||||
this.next = block.getUint16(DIRECTORY_OFFSETS.NEXT, true);
|
||||
this.storageType = block.getUint8(DIRECTORY_OFFSETS.STORAGE_TYPE) >> 4;
|
||||
const nameLength = block.getUint8(DIRECTORY_OFFSETS.NAME_LENGTH) & 0xF;
|
||||
const caseBits = block.getUint8(DIRECTORY_OFFSETS.CASE_BITS);
|
||||
this.name = readFileName(block, DIRECTORY_OFFSETS.DIRECTORY_NAME, nameLength, caseBits);
|
||||
this.creation = uint32ToDate(block.getUint32(DIRECTORY_OFFSETS.CREATION, true));
|
||||
this.access = block.getUint8(DIRECTORY_OFFSETS.ACCESS);
|
||||
this.entryLength = block.getUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH);
|
||||
this.entriesPerBlock = block.getUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK);
|
||||
this.fileCount = block.getUint16(DIRECTORY_OFFSETS.FILE_COUNT, true);
|
||||
this.parent = block.getUint16(DIRECTORY_OFFSETS.PARENT, true);
|
||||
this.parentEntryNumber = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER);
|
||||
this.parentEntryLength = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH);
|
||||
|
||||
this.entries = readEntries(this.volume, block, this);
|
||||
}
|
||||
|
||||
write() {
|
||||
const block = new DataView(this.blocks[this.fileEntry.keyPointer].buffer);
|
||||
|
||||
const nameLength = this.name.length & 0x0f;
|
||||
block.setUint8(DIRECTORY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
|
||||
const caseBits = writeFileName(block, DIRECTORY_OFFSETS.DIRECTORY_NAME, this.name);
|
||||
block.setUint32(DIRECTORY_OFFSETS.CREATION, dateToUint32(this.creation), true);
|
||||
block.setUint16(DIRECTORY_OFFSETS.CASE_BITS, caseBits);
|
||||
block.setUint8(DIRECTORY_OFFSETS.ACCESS, this.access);
|
||||
block.setUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH, this.entryLength);
|
||||
block.setUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
|
||||
block.setUint16(DIRECTORY_OFFSETS.FILE_COUNT, this.fileCount, true);
|
||||
block.setUint16(DIRECTORY_OFFSETS.PARENT, this.parent, true);
|
||||
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER, this.parentEntryNumber);
|
||||
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH, this.parentEntryLength);
|
||||
|
||||
writeEntries(this.volume, block, this.vdh);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
141
js/formats/prodos/file_entry.ts
Normal file
141
js/formats/prodos/file_entry.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
|
||||
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
|
||||
import type { byte, word } from 'js/types';
|
||||
import { ProDOSVolume } from '.';
|
||||
import { VDH } from './vdh';
|
||||
import { Directory } from './directory';
|
||||
|
||||
const ENTRY_OFFSETS = {
|
||||
STORAGE_TYPE: 0x00,
|
||||
NAME_LENGTH: 0x00,
|
||||
FILE_NAME: 0x01,
|
||||
FILE_TYPE: 0x10,
|
||||
KEY_POINTER: 0x11,
|
||||
BLOCKS_USED: 0x13,
|
||||
EOF: 0x15,
|
||||
CREATION: 0x18,
|
||||
CASE_BITS: 0x1C,
|
||||
VERSION: 0x1C,
|
||||
MIN_VERSION: 0x1D,
|
||||
ACCESS: 0x1E,
|
||||
AUX_TYPE: 0x1F,
|
||||
LAST_MOD: 0x21,
|
||||
HEADER_POINTER: 0x25
|
||||
} as const;
|
||||
|
||||
export class FileEntry {
|
||||
block: DataView;
|
||||
offset: word;
|
||||
|
||||
storageType: byte = STORAGE_TYPES.SEEDLING;
|
||||
name: string = 'Untitled';
|
||||
fileType: byte = 0;
|
||||
auxType: word = 0;
|
||||
blocksUsed: word = 0;
|
||||
eof: number = 0;
|
||||
access: byte = ACCESS_TYPES.ALL;
|
||||
creation: Date = new Date();
|
||||
lastMod: Date = new Date();
|
||||
keyPointer: word = 0;
|
||||
headerPointer: word = 0;
|
||||
|
||||
read(block: DataView, offset: word) {
|
||||
this.block = block;
|
||||
this.offset = offset;
|
||||
|
||||
this.storageType = block.getUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE) >> 4;
|
||||
const nameLength = block.getUint8(offset + ENTRY_OFFSETS.NAME_LENGTH) & 0xF;
|
||||
const caseBits = block.getUint16(offset + ENTRY_OFFSETS.CASE_BITS, true);
|
||||
this.name = readFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, nameLength, caseBits);
|
||||
this.fileType = block.getUint8(offset + ENTRY_OFFSETS.FILE_TYPE);
|
||||
this.keyPointer = block.getUint16(offset + ENTRY_OFFSETS.KEY_POINTER, true);
|
||||
this.blocksUsed = block.getUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, true);
|
||||
this.eof =
|
||||
block.getUint8(offset + ENTRY_OFFSETS.EOF) |
|
||||
block.getUint8(offset + ENTRY_OFFSETS.EOF + 1) << 8 |
|
||||
block.getUint8(offset + ENTRY_OFFSETS.EOF + 2) << 16;
|
||||
this.creation = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.CREATION, true));
|
||||
this.access = block.getUint8(offset + ENTRY_OFFSETS.ACCESS);
|
||||
this.auxType = block.getUint16(offset + ENTRY_OFFSETS.AUX_TYPE, true);
|
||||
this.lastMod = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.LAST_MOD, true));
|
||||
this.headerPointer = block.getUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, true);
|
||||
}
|
||||
|
||||
write(block?: DataView, offset?: word) {
|
||||
this.block = block ?? this.block;
|
||||
this.offset = offset ?? this.offset;
|
||||
|
||||
const nameLength = this.name.length & 0x0f;
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
|
||||
const caseBits = writeFileName(this.block, this.offset + ENTRY_OFFSETS.FILE_NAME, this.name);
|
||||
this.block.setUint16(this.offset + ENTRY_OFFSETS.CASE_BITS, caseBits);
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.FILE_TYPE, this.fileType);
|
||||
this.block.setUint16(this.offset + ENTRY_OFFSETS.KEY_POINTER, this.keyPointer, true);
|
||||
this.block.setUint16(this.offset + ENTRY_OFFSETS.BLOCKS_USED, this.blocksUsed, true);
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF, this.eof & 0xff);
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF + 1, (this.eof && 0xff00) >> 8);
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.EOF + 2, this.eof >> 16);
|
||||
this.block.setUint32(this.offset + ENTRY_OFFSETS.CREATION, dateToUint32(this.creation), true);
|
||||
this.block.setUint8(this.offset + ENTRY_OFFSETS.ACCESS, this.access);
|
||||
this.block.setUint16(this.offset + ENTRY_OFFSETS.AUX_TYPE, this.auxType, true);
|
||||
this.block.setUint32(this.offset + ENTRY_OFFSETS.LAST_MOD, dateToUint32(this.lastMod), true);
|
||||
this.block.setUint16(this.offset + ENTRY_OFFSETS.HEADER_POINTER, this.headerPointer, true);
|
||||
}
|
||||
}
|
||||
|
||||
export function readEntries(volume: ProDOSVolume, block: DataView, header: VDH | Directory) {
|
||||
const blocks = volume.blocks();
|
||||
const entries = [];
|
||||
let offset = header.entryLength + 0x4;
|
||||
let count = 2;
|
||||
let next = header.next;
|
||||
|
||||
for (let idx = 0; idx < header.fileCount; idx++) {
|
||||
const fileEntry = new FileEntry();
|
||||
fileEntry.read(block, offset);
|
||||
entries.push(fileEntry);
|
||||
offset += header.entryLength;
|
||||
count++;
|
||||
if (count >= header.entriesPerBlock) {
|
||||
block = new DataView(blocks[next].buffer);
|
||||
next = block.getUint16(0x02, true);
|
||||
offset = 0x4;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function writeEntries(volume: ProDOSVolume, block: DataView, header: VDH | Directory) {
|
||||
const blocks = volume.blocks();
|
||||
const bitMap = volume.bitMap();
|
||||
let offset = header.entryLength + 0x4;
|
||||
let count = 2;
|
||||
let next = header.next;
|
||||
|
||||
for (let idx = 0; idx < header.fileCount; idx++) {
|
||||
const fileEntry = header.entries[idx];
|
||||
fileEntry.write(block, offset);
|
||||
offset += header.entryLength;
|
||||
count++;
|
||||
if (count >= header.entriesPerBlock) {
|
||||
const prev = next;
|
||||
if (!next) {
|
||||
next = bitMap.allocBlock();
|
||||
}
|
||||
block = new DataView(blocks[next].buffer);
|
||||
block.setUint16(0x00, prev, true);
|
||||
next = block.getUint16(0x02, true);
|
||||
offset = 0x4;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
next = block.getUint16(0x02, true);
|
||||
block.setUint16(0x02, 0, true);
|
||||
while (next) {
|
||||
block = new DataView(blocks[next].buffer);
|
||||
bitMap.freeBlock(next);
|
||||
next = block.getUint16(0x02, true);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
33
js/formats/prodos/index.ts
Normal file
33
js/formats/prodos/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { VDH } from './vdh';
|
||||
import { BitMap } from './bit_map';
|
||||
import { BlockDisk } from '../types';
|
||||
|
||||
export class ProDOSVolume {
|
||||
_vdh: VDH;
|
||||
_bitMap: BitMap;
|
||||
|
||||
constructor(private _disk: BlockDisk) {}
|
||||
|
||||
disk() {
|
||||
return this._disk;
|
||||
}
|
||||
|
||||
blocks() {
|
||||
return this._disk.blocks;
|
||||
}
|
||||
|
||||
vdh() {
|
||||
if (!this._vdh) {
|
||||
this._vdh = new VDH(this);
|
||||
this._vdh.read();
|
||||
}
|
||||
return this._vdh;
|
||||
}
|
||||
|
||||
bitMap() {
|
||||
if (!this._bitMap) {
|
||||
this._bitMap = new BitMap(this);
|
||||
}
|
||||
return this._bitMap;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
83
js/formats/prodos/sapling_file.ts
Normal file
83
js/formats/prodos/sapling_file.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { ProDOSVolume } from '.';
|
||||
import type { BitMap } from './bit_map';
|
||||
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
|
||||
import { FileEntry } from './file_entry';
|
||||
|
||||
export class SaplingFile {
|
||||
blocks: Uint8Array[];
|
||||
bitMap: BitMap;
|
||||
|
||||
constructor(private volume: ProDOSVolume, private fileEntry: FileEntry) {
|
||||
this.blocks = this.volume.blocks();
|
||||
this.bitMap = this.volume.bitMap();
|
||||
|
||||
}
|
||||
|
||||
getBlockPointers() {
|
||||
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const seedlingPointers = new DataView(saplingBlock);
|
||||
|
||||
const pointers = [this.fileEntry.keyPointer];
|
||||
for (let idx = 0; idx < 256; idx++) {
|
||||
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
|
||||
if (seedlingPointer) {
|
||||
pointers.push(seedlingPointer);
|
||||
}
|
||||
}
|
||||
return pointers;
|
||||
}
|
||||
|
||||
read() {
|
||||
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const seedlingPointers = new DataView(saplingBlock);
|
||||
|
||||
let remainingLength = this.fileEntry.eof;
|
||||
const data = new Uint8Array(remainingLength);
|
||||
let offset = 0;
|
||||
let idx = 0;
|
||||
while (remainingLength > 0) {
|
||||
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
|
||||
if (seedlingPointer) {
|
||||
const seedlingBlock = this.blocks[seedlingPointer];
|
||||
const bytes = seedlingBlock.slice(0, Math.min(BLOCK_SIZE, remainingLength));
|
||||
|
||||
data.set(bytes, offset);
|
||||
}
|
||||
idx++;
|
||||
offset += BLOCK_SIZE;
|
||||
remainingLength -= BLOCK_SIZE;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
write(data: Uint8Array) {
|
||||
this.fileEntry.storageType = STORAGE_TYPES.SAPLING;
|
||||
this.fileEntry.keyPointer = this.bitMap.allocBlock();
|
||||
this.fileEntry.eof = data.byteLength;
|
||||
const saplingBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const seedlingPointers = new DataView(saplingBlock);
|
||||
|
||||
let remainingLength = data.byteLength;
|
||||
let offset = 0;
|
||||
let idx = 0;
|
||||
|
||||
while (remainingLength > 0) {
|
||||
const seedlingPointer = this.bitMap.allocBlock();
|
||||
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
|
||||
const seedlingBlock = this.blocks[seedlingPointer];
|
||||
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
|
||||
idx++;
|
||||
offset += BLOCK_SIZE;
|
||||
remainingLength -= BLOCK_SIZE;
|
||||
}
|
||||
this.fileEntry.write();
|
||||
}
|
||||
|
||||
delete() {
|
||||
const pointers = this.getBlockPointers();
|
||||
for (let idx = 0; idx < pointers.length; idx++) {
|
||||
this.bitMap.freeBlock(pointers[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
46
js/formats/prodos/seedling_file.ts
Normal file
46
js/formats/prodos/seedling_file.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { ProDOSVolume } from '.';
|
||||
import { BitMap } from './bit_map';
|
||||
import { STORAGE_TYPES } from './constants';
|
||||
import { FileEntry } from './file_entry';
|
||||
|
||||
export class SeedlingFile {
|
||||
blocks: Uint8Array[];
|
||||
bitMap: BitMap;
|
||||
|
||||
constructor(volume: ProDOSVolume, private fileEntry: FileEntry) {
|
||||
this.blocks = volume.blocks();
|
||||
this.bitMap = volume.bitMap();
|
||||
}
|
||||
|
||||
getBlockPointers() {
|
||||
const pointers = [this.fileEntry.keyPointer];
|
||||
return pointers;
|
||||
}
|
||||
|
||||
read() {
|
||||
const seedlingBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const data = new Uint8Array(this.fileEntry.eof);
|
||||
data.set(seedlingBlock.slice(0, this.fileEntry.eof));
|
||||
return data;
|
||||
}
|
||||
|
||||
write(data: Uint8Array) {
|
||||
if (this.fileEntry.keyPointer) {
|
||||
this.delete();
|
||||
}
|
||||
this.fileEntry.storageType = STORAGE_TYPES.SEEDLING;
|
||||
this.fileEntry.keyPointer = this.bitMap.allocBlock();
|
||||
this.fileEntry.eof = data.byteLength;
|
||||
const seedlingBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
seedlingBlock.set(data);
|
||||
this.fileEntry.write();
|
||||
}
|
||||
|
||||
delete() {
|
||||
const pointers = this.getBlockPointers();
|
||||
for (let idx = 0; idx < pointers.length; idx++) {
|
||||
this.bitMap.freeBlock(pointers[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
112
js/formats/prodos/tree_file.ts
Normal file
112
js/formats/prodos/tree_file.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import type { ProDOSVolume } from '.';
|
||||
import { BitMap } from './bit_map';
|
||||
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
|
||||
import type { FileEntry } from './file_entry';
|
||||
|
||||
export class TreeFile {
|
||||
private bitMap: BitMap;
|
||||
private blocks: Uint8Array[];
|
||||
|
||||
constructor (volume: ProDOSVolume, private fileEntry: FileEntry) {
|
||||
this.blocks = volume.blocks();
|
||||
this.bitMap = volume.bitMap();
|
||||
}
|
||||
|
||||
getBlockPointers() {
|
||||
const treeBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const saplingPointers = new DataView(treeBlock);
|
||||
const pointers = [];
|
||||
for (let idx = 0; idx < 256; idx++) {
|
||||
const saplingPointer = saplingPointers.getUint16(idx * 2);
|
||||
if (saplingPointer) {
|
||||
pointers.push(saplingPointer);
|
||||
const seedlingPointers = new DataView(this.blocks[saplingPointer]);
|
||||
for (let jdx = 0; jdx < 256; jdx++) {
|
||||
const seedlingPointer = seedlingPointers.getUint16(idx * 2);
|
||||
if (seedlingPointer) {
|
||||
pointers.push(seedlingPointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointers;
|
||||
}
|
||||
|
||||
read() {
|
||||
const treeBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const saplingPointers = new DataView(treeBlock);
|
||||
let remainingLength = this.fileEntry.eof;
|
||||
const data = new Uint8Array(remainingLength);
|
||||
let offset = 0;
|
||||
let idx = 0;
|
||||
|
||||
while (remainingLength > 0) {
|
||||
const saplingPointer = saplingPointers.getUint16(idx * 2, true);
|
||||
let jdx = 0;
|
||||
if (saplingPointer) {
|
||||
const saplingBlock = this.blocks[saplingPointer];
|
||||
const seedlingPointers = new DataView(saplingBlock);
|
||||
|
||||
while (jdx < 256 && remainingLength > 0) {
|
||||
const seedlingPointer = seedlingPointers.getUint16(idx * 2, true);
|
||||
if (seedlingPointer) {
|
||||
const seedlingBlock = this.blocks[seedlingPointer];
|
||||
const bytes = seedlingBlock.slice(Math.min(BLOCK_SIZE, remainingLength));
|
||||
|
||||
data.set(bytes, offset);
|
||||
}
|
||||
jdx++;
|
||||
offset += BLOCK_SIZE;
|
||||
remainingLength -= BLOCK_SIZE;
|
||||
}
|
||||
} else {
|
||||
offset += BLOCK_SIZE * 256;
|
||||
remainingLength -= BLOCK_SIZE * 256;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
write(data: Uint8Array) {
|
||||
this.fileEntry.storageType = STORAGE_TYPES.TREE;
|
||||
this.fileEntry.keyPointer = this.bitMap.allocBlock();
|
||||
this.fileEntry.eof = data.byteLength;
|
||||
|
||||
const treeBlock = this.blocks[this.fileEntry.keyPointer];
|
||||
const saplingPointers = new DataView(treeBlock);
|
||||
|
||||
let remainingLength = this.fileEntry.eof;
|
||||
let offset = 0;
|
||||
let idx = 0;
|
||||
|
||||
while (remainingLength > 0) {
|
||||
const saplingPointer = this.bitMap.allocBlock();
|
||||
const saplingBlock = this.blocks[saplingPointer];
|
||||
saplingPointers.setUint16(idx * 2, saplingPointer, true);
|
||||
const seedlingPointers = new DataView(saplingBlock);
|
||||
|
||||
let jdx = 0;
|
||||
|
||||
while (jdx < 256 && remainingLength > 0) {
|
||||
const seedlingPointer = this.bitMap.allocBlock();
|
||||
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
|
||||
const seedlingBlock = this.blocks[seedlingPointer];
|
||||
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
|
||||
jdx++;
|
||||
offset += BLOCK_SIZE;
|
||||
remainingLength -= BLOCK_SIZE;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
this.fileEntry.write();
|
||||
}
|
||||
|
||||
delete() {
|
||||
const pointers = this.getBlockPointers();
|
||||
for (let idx = 0; idx < pointers.length; idx++) {
|
||||
this.bitMap.freeBlock(pointers[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
0
js/formats/prodos/types.ts
Normal file
0
js/formats/prodos/types.ts
Normal file
@ -1,97 +0,0 @@
|
||||
import { debug } from '../../util';
|
||||
import { STORAGE_TYPES } from './constants';
|
||||
import { Directory } from './directory';
|
||||
|
||||
export function uint32ToDate(val) {
|
||||
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
|
||||
|
||||
if (val) {
|
||||
var yearMonthDay = val & 0xffff;
|
||||
var hourMinute = val >> 16;
|
||||
|
||||
var year = yearMonthDay >> 9;
|
||||
var month = (yearMonthDay & 0x01E0) >> 5;
|
||||
var day = yearMonthDay & 0x001F;
|
||||
|
||||
var hour = hourMinute >> 8;
|
||||
var min = hourMinute & 0xff;
|
||||
|
||||
return new Date(1900 + year, month - 1, day, hour, min);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function dateToUint32(date) {
|
||||
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
|
||||
|
||||
var val = 0;
|
||||
|
||||
if (date) {
|
||||
var year = date.getYear() - 1900;
|
||||
var month = date.getMonth() + 1;
|
||||
var day = date.getDate();
|
||||
|
||||
var hour = date.getHour();
|
||||
var min = date.getMinute();
|
||||
|
||||
var yearMonthDay = year << 9 | month << 5 | day;
|
||||
var hourMinute = hour << 8 | min;
|
||||
val = hourMinute << 16 | yearMonthDay;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export function readFileName(block, offset, nameLength, caseBits) {
|
||||
var name = '';
|
||||
if (!(caseBits & 0x8000)) {
|
||||
caseBits = 0;
|
||||
}
|
||||
for (var idx = 0; idx < nameLength; idx++) {
|
||||
caseBits <<= 1;
|
||||
var char = String.fromCharCode(block.getUint8(offset + idx));
|
||||
name += caseBits & 0x8000 ? char.toLowerCase() : char;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function writeFileName(block, offset, name) {
|
||||
var caseBits = 0;
|
||||
for (var idx = 0; idx < name.length; idx++) {
|
||||
caseBits <<= 1;
|
||||
var charCode = name.charCodeAt(idx);
|
||||
if (charCode > 0x60 && charCode < 0x7B) {
|
||||
caseBits |= 0x1;
|
||||
charCode -= 0x20;
|
||||
}
|
||||
block.setUint8(offset + idx, charCode);
|
||||
}
|
||||
return caseBits;
|
||||
}
|
||||
|
||||
export function dumpDirectory(volume, dirEntry, depth) {
|
||||
var dir = new Directory(volume, dirEntry);
|
||||
dir.read();
|
||||
|
||||
for (var idx = 0; idx < dir.entries.length; idx++) {
|
||||
var fileEntry = dir.entries[idx];
|
||||
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
|
||||
debug(depth, fileEntry.name);
|
||||
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
|
||||
dumpDirectory(volume, fileEntry, depth + ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dump(volume) {
|
||||
var vdh = volume.vdh();
|
||||
debug(vdh.name);
|
||||
for (var idx = 0; idx < vdh.entries.length; idx++) {
|
||||
var fileEntry = vdh.entries[idx];
|
||||
debug(fileEntry.name);
|
||||
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
|
||||
dumpDirectory(volume, fileEntry, ' ');
|
||||
}
|
||||
}
|
||||
}
|
100
js/formats/prodos/utils.ts
Normal file
100
js/formats/prodos/utils.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { debug } from '../../util';
|
||||
import { STORAGE_TYPES } from './constants';
|
||||
import { Directory } from './directory';
|
||||
import type { byte, word } from 'js/types';
|
||||
import { ProDOSVolume } from '.';
|
||||
import { FileEntry } from './file_entry';
|
||||
|
||||
export function uint32ToDate(val: word) {
|
||||
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
|
||||
|
||||
if (val) {
|
||||
const yearMonthDay = val & 0xffff;
|
||||
const hourMinute = val >> 16;
|
||||
|
||||
const year = yearMonthDay >> 9;
|
||||
const month = (yearMonthDay & 0x01E0) >> 5;
|
||||
const day = yearMonthDay & 0x001F;
|
||||
|
||||
const hour = hourMinute >> 8;
|
||||
const min = hourMinute & 0xff;
|
||||
|
||||
return new Date(1900 + year, month - 1, day, hour, min);
|
||||
}
|
||||
return new Date(0);
|
||||
}
|
||||
|
||||
export function dateToUint32(date: Date) {
|
||||
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
|
||||
|
||||
let val = 0;
|
||||
|
||||
if (date) {
|
||||
const year = date.getFullYear() - 1900;
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
|
||||
const hour = date.getHours();
|
||||
const min = date.getMinutes();
|
||||
|
||||
const yearMonthDay = year << 9 | month << 5 | day;
|
||||
const hourMinute = hour << 8 | min;
|
||||
val = hourMinute << 16 | yearMonthDay;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export function readFileName(block: DataView, offset: word, nameLength: byte, caseBits: word) {
|
||||
let name = '';
|
||||
if (!(caseBits & 0x8000)) {
|
||||
caseBits = 0;
|
||||
}
|
||||
for (let idx = 0; idx < nameLength; idx++) {
|
||||
caseBits <<= 1;
|
||||
const char = String.fromCharCode(block.getUint8(offset + idx));
|
||||
name += caseBits & 0x8000 ? char.toLowerCase() : char;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function writeFileName(block: DataView, offset: word, name: string) {
|
||||
let caseBits = 0;
|
||||
for (let idx = 0; idx < name.length; idx++) {
|
||||
caseBits <<= 1;
|
||||
let charCode = name.charCodeAt(idx);
|
||||
if (charCode > 0x60 && charCode < 0x7B) {
|
||||
caseBits |= 0x1;
|
||||
charCode -= 0x20;
|
||||
}
|
||||
block.setUint8(offset + idx, charCode);
|
||||
}
|
||||
return caseBits;
|
||||
}
|
||||
|
||||
export function dumpDirectory(volume: ProDOSVolume, dirEntry: FileEntry, depth: string) {
|
||||
const dir = new Directory(volume, dirEntry);
|
||||
dir.read();
|
||||
|
||||
for (let idx = 0; idx < dir.entries.length; idx++) {
|
||||
const fileEntry = dir.entries[idx];
|
||||
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
|
||||
debug(depth, fileEntry.name);
|
||||
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
|
||||
dumpDirectory(volume, fileEntry, depth + ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dump(volume: ProDOSVolume) {
|
||||
const vdh = volume.vdh();
|
||||
debug(vdh.name);
|
||||
for (let idx = 0; idx < vdh.entries.length; idx++) {
|
||||
const fileEntry = vdh.entries[idx];
|
||||
debug(fileEntry.name);
|
||||
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
|
||||
dumpDirectory(volume, fileEntry, ' ');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
|
||||
import { readEntries, writeEntries } from './file_entry';
|
||||
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
|
||||
|
||||
export function VDH(volume) {
|
||||
var blocks = volume.blocks();
|
||||
|
||||
var VDH_BLOCK = 2;
|
||||
var VDH_OFFSETS = {
|
||||
PREV: 0x00,
|
||||
NEXT: 0x02,
|
||||
STORAGE_TYPE: 0x04,
|
||||
NAME_LENGTH: 0x04,
|
||||
VOLUME_NAME: 0x05,
|
||||
RESERVED_1: 0x14,
|
||||
CASE_BITS: 0x1A,
|
||||
CREATION: 0x1C,
|
||||
VERSION: 0x20,
|
||||
MIN_VERSION: 0x21,
|
||||
ACCESS: 0x22,
|
||||
ENTRY_LENGTH: 0x23,
|
||||
ENTRIES_PER_BLOCK: 0x24,
|
||||
FILE_COUNT: 0x25,
|
||||
BIT_MAP_POINTER: 0x27,
|
||||
TOTAL_BLOCKS: 0x29,
|
||||
};
|
||||
|
||||
return {
|
||||
prev: 0,
|
||||
next: 0,
|
||||
storageType: STORAGE_TYPES.VDH_HEADER,
|
||||
name: '',
|
||||
creation: new Date(),
|
||||
access: ACCESS_TYPES.ALL,
|
||||
entryLength: 0x27,
|
||||
entriesPerBlock: 23,
|
||||
fileCount: 0,
|
||||
bitMapPointer: 0,
|
||||
totalBlocks: 0,
|
||||
entries: [],
|
||||
|
||||
read: function () {
|
||||
var block = new DataView(blocks[VDH_BLOCK].buffer);
|
||||
|
||||
this.next = block.getUint16(VDH_OFFSETS.NEXT, true);
|
||||
this.storageType = block.getUint8(VDH_OFFSETS.STORAGE_TYPE) >> 4;
|
||||
var nameLength = block.getUint8(VDH_OFFSETS.NAME_LENGTH) & 0xF;
|
||||
var caseBits = block.getUint8(VDH_OFFSETS.CASE_BITS);
|
||||
this.name = readFileName(block, VDH_OFFSETS.VOLUME_NAME, nameLength, caseBits);
|
||||
this.creation = uint32ToDate(block.getUint32(VDH_OFFSETS.CREATION, true));
|
||||
this.access = block.getUint8(VDH_OFFSETS.ACCESS);
|
||||
this.entryLength = block.getUint8(VDH_OFFSETS.ENTRY_LENGTH);
|
||||
this.entriesPerBlock = block.getUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK);
|
||||
this.fileCount = block.getUint16(VDH_OFFSETS.FILE_COUNT, true);
|
||||
this.bitMapPointer = block.getUint16(VDH_OFFSETS.BIT_MAP_POINTER, true);
|
||||
this.totalBlocks = block.getUint16(VDH_OFFSETS.TOTAL_BLOCKS, true);
|
||||
|
||||
this.entries = readEntries(volume, block, this);
|
||||
},
|
||||
|
||||
write: function() {
|
||||
var block = new DataView(blocks[VDH_BLOCK].buffer);
|
||||
|
||||
var nameLength = name.length & 0x0f;
|
||||
block.setUint8(VDH_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
|
||||
var caseBits = writeFileName(block, VDH_OFFSETS.FILE_NAME, this.name);
|
||||
block.setUint32(VDH_OFFSETS.CREATION, dateToUint32(this.creation), true);
|
||||
block.setUint16(VDH_OFFSETS.CASE_BITS, caseBits);
|
||||
block.setUint8(VDH_OFFSETS.ACCESS, this.access);
|
||||
block.setUint8(VDH_OFFSETS.ENTRY_LENGTH, this.entryLength);
|
||||
block.setUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
|
||||
block.setUint16(VDH_OFFSETS.FILE_COUNT, this.fileCount, true);
|
||||
block.setUint16(VDH_OFFSETS.BIT_MAP_POINTER, this.bitMapPointer, true);
|
||||
block.setUint16(VDH_OFFSETS.TOTAL_BLOCKS, this.totalBlocks, true);
|
||||
|
||||
writeEntries(blocks, block, this);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
84
js/formats/prodos/vdh.ts
Normal file
84
js/formats/prodos/vdh.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
|
||||
import { FileEntry, readEntries, writeEntries } from './file_entry';
|
||||
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
|
||||
import { byte, word } from 'js/types';
|
||||
import { ProDOSVolume } from '.';
|
||||
|
||||
const VDH_BLOCK = 2;
|
||||
const VDH_OFFSETS = {
|
||||
PREV: 0x00,
|
||||
NEXT: 0x02,
|
||||
STORAGE_TYPE: 0x04,
|
||||
NAME_LENGTH: 0x04,
|
||||
VOLUME_NAME: 0x05,
|
||||
RESERVED_1: 0x14,
|
||||
CASE_BITS: 0x1A,
|
||||
CREATION: 0x1C,
|
||||
VERSION: 0x20,
|
||||
MIN_VERSION: 0x21,
|
||||
ACCESS: 0x22,
|
||||
ENTRY_LENGTH: 0x23,
|
||||
ENTRIES_PER_BLOCK: 0x24,
|
||||
FILE_COUNT: 0x25,
|
||||
BIT_MAP_POINTER: 0x27,
|
||||
TOTAL_BLOCKS: 0x29,
|
||||
} as const;
|
||||
|
||||
export class VDH {
|
||||
private blocks: Uint8Array[];
|
||||
|
||||
prev: word = 0;
|
||||
next: word = 0;
|
||||
storageType: byte = STORAGE_TYPES.VDH_HEADER;
|
||||
name: string = '';
|
||||
creation: Date = new Date();
|
||||
access: byte = ACCESS_TYPES.ALL;
|
||||
entryLength = 0x27;
|
||||
entriesPerBlock = 23;
|
||||
fileCount = 0;
|
||||
bitMapPointer: word = 0;
|
||||
totalBlocks: word = 0;
|
||||
entries: FileEntry[] = [];
|
||||
|
||||
constructor(private volume: ProDOSVolume) {
|
||||
this.blocks = this.volume.blocks();
|
||||
}
|
||||
|
||||
|
||||
read() {
|
||||
const block = new DataView(this.blocks[VDH_BLOCK].buffer);
|
||||
|
||||
this.next = block.getUint16(VDH_OFFSETS.NEXT, true);
|
||||
this.storageType = block.getUint8(VDH_OFFSETS.STORAGE_TYPE) >> 4;
|
||||
const nameLength = block.getUint8(VDH_OFFSETS.NAME_LENGTH) & 0xF;
|
||||
const caseBits = block.getUint8(VDH_OFFSETS.CASE_BITS);
|
||||
this.name = readFileName(block, VDH_OFFSETS.VOLUME_NAME, nameLength, caseBits);
|
||||
this.creation = uint32ToDate(block.getUint32(VDH_OFFSETS.CREATION, true));
|
||||
this.access = block.getUint8(VDH_OFFSETS.ACCESS);
|
||||
this.entryLength = block.getUint8(VDH_OFFSETS.ENTRY_LENGTH);
|
||||
this.entriesPerBlock = block.getUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK);
|
||||
this.fileCount = block.getUint16(VDH_OFFSETS.FILE_COUNT, true);
|
||||
this.bitMapPointer = block.getUint16(VDH_OFFSETS.BIT_MAP_POINTER, true);
|
||||
this.totalBlocks = block.getUint16(VDH_OFFSETS.TOTAL_BLOCKS, true);
|
||||
|
||||
this.entries = readEntries(this.volume, block, this);
|
||||
}
|
||||
|
||||
write() {
|
||||
const block = new DataView(this.blocks[VDH_BLOCK].buffer);
|
||||
|
||||
const nameLength = this.name.length & 0x0f;
|
||||
block.setUint8(VDH_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
|
||||
const caseBits = writeFileName(block, VDH_OFFSETS.VOLUME_NAME, this.name);
|
||||
block.setUint32(VDH_OFFSETS.CREATION, dateToUint32(this.creation), true);
|
||||
block.setUint16(VDH_OFFSETS.CASE_BITS, caseBits);
|
||||
block.setUint8(VDH_OFFSETS.ACCESS, this.access);
|
||||
block.setUint8(VDH_OFFSETS.ENTRY_LENGTH, this.entryLength);
|
||||
block.setUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
|
||||
block.setUint16(VDH_OFFSETS.FILE_COUNT, this.fileCount, true);
|
||||
block.setUint16(VDH_OFFSETS.BIT_MAP_POINTER, this.bitMapPointer, true);
|
||||
block.setUint16(VDH_OFFSETS.TOTAL_BLOCKS, this.totalBlocks, true);
|
||||
|
||||
writeEntries(this.volume, block, this);
|
||||
}
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
export default function IntBasicDump(mem)
|
||||
{
|
||||
var _mem = mem;
|
||||
|
||||
var LETTERS =
|
||||
' ' +
|
||||
' !"#$%&\'()*+,-./0123456789:;<=>?' +
|
||||
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_' +
|
||||
'`abcdefghijklmnopqrstuvwxyz{|}~ ';
|
||||
|
||||
var TOKENS = {
|
||||
0x02: '_',
|
||||
0x03: ':',
|
||||
0x04: 'LOAD',
|
||||
0x05: 'SAVE',
|
||||
0x06: 'CON',
|
||||
0x07: 'RUN',
|
||||
0x08: 'RUN',
|
||||
0x09: 'DEL',
|
||||
0x0A: ',',
|
||||
0x0B: 'NEW',
|
||||
0x0C: 'CLR',
|
||||
0x0D: 'AUTO',
|
||||
0x0E: ',',
|
||||
0x0F: 'MAN',
|
||||
0x10: 'HIMEM:',
|
||||
0x11: 'LOMEM:',
|
||||
0x12: '+',
|
||||
0x13: '-',
|
||||
0x14: '*',
|
||||
0x15: '/',
|
||||
0x16: '=',
|
||||
0x17: '#',
|
||||
0x18: '>=',
|
||||
0x19: '>',
|
||||
0x1A: '<=',
|
||||
0x1B: '<>',
|
||||
0x1C: '<',
|
||||
0x1D: 'AND',
|
||||
0x1E: 'OR',
|
||||
0x1F: 'MOD',
|
||||
0x20: '^',
|
||||
0x21: '+',
|
||||
0x22: '(',
|
||||
0x23: ',',
|
||||
0x24: 'THEN',
|
||||
0x25: 'THEN',
|
||||
0x26: ',',
|
||||
0x27: ',',
|
||||
0x28: '\'',
|
||||
0x29: '\'',
|
||||
0x2A: '(',
|
||||
0x2B: '!',
|
||||
0x2C: '!',
|
||||
0x2D: '(',
|
||||
0x2E: 'PEEK',
|
||||
0x2F: 'RND',
|
||||
0x30: 'SGN',
|
||||
0x31: 'ABS',
|
||||
0x32: 'PDL',
|
||||
0x33: 'RNDX',
|
||||
0x34: '(',
|
||||
0x35: '+',
|
||||
0x36: '-',
|
||||
0x37: 'NOT',
|
||||
0x38: '(',
|
||||
0x39: '=',
|
||||
0x3A: '#',
|
||||
0x3B: 'LEN(',
|
||||
0x3C: 'ASC(',
|
||||
0x3D: 'SCRN(',
|
||||
0x3E: ',',
|
||||
0x3F: '(',
|
||||
0x40: '$',
|
||||
0x41: '$',
|
||||
0x42: '(',
|
||||
0x43: ',',
|
||||
0x44: ',',
|
||||
0x45: ';',
|
||||
0x46: ';',
|
||||
0x47: ';',
|
||||
0x48: ',',
|
||||
0x49: ',',
|
||||
0x4A: ',',
|
||||
0x4B: 'TEXT',
|
||||
0x4C: 'GR',
|
||||
0x4D: 'CALL',
|
||||
0x4E: 'DIM',
|
||||
0x4F: 'DIM',
|
||||
0x50: 'TAB',
|
||||
0x51: 'END',
|
||||
0x52: 'INPUT',
|
||||
0x53: 'INPUT',
|
||||
0x54: 'INPUT',
|
||||
0x55: 'FOR',
|
||||
0x56: '=',
|
||||
0x57: 'TO',
|
||||
0x58: 'STEP',
|
||||
0x59: 'NEXT',
|
||||
0x5A: ',',
|
||||
0x5B: 'RETURN',
|
||||
0x5C: 'GOSUB',
|
||||
0x5D: 'REM',
|
||||
0x5E: 'LET',
|
||||
0x5F: 'GOTO',
|
||||
0x60: 'IF',
|
||||
0x61: 'PRINT',
|
||||
0x62: 'PRINT',
|
||||
0x63: 'PRINT',
|
||||
0x64: 'POKE',
|
||||
0x65: ',',
|
||||
0x66: 'COLOR=',
|
||||
0x67: 'PLOT',
|
||||
0x68: ',',
|
||||
0x69: 'HLIN',
|
||||
0x6A: ',',
|
||||
0x6B: 'AT',
|
||||
0x6C: 'VLIN',
|
||||
0x6E: 'AT',
|
||||
0x6F: 'VTAB',
|
||||
0x70: '=',
|
||||
0x71: '=',
|
||||
0x72: ')',
|
||||
0x73: ')',
|
||||
0x74: 'LIST',
|
||||
0x76: 'LIST',
|
||||
0x77: 'POP',
|
||||
0x78: 'NODSP',
|
||||
0x79: 'NODSP',
|
||||
0x7A: 'NOTRACE',
|
||||
0x7B: 'DSP',
|
||||
0x7C: 'DSP',
|
||||
0x7D: 'TRACE',
|
||||
0x7E: 'PR#',
|
||||
0x7F: 'IN#'
|
||||
};
|
||||
|
||||
function readByte(addr) {
|
||||
var page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
return _mem.read(page, off);
|
||||
}
|
||||
|
||||
function readWord(addr) {
|
||||
var lsb, msb;
|
||||
|
||||
lsb = readByte(addr);
|
||||
msb = readByte(addr + 1);
|
||||
|
||||
return (msb << 8) | lsb;
|
||||
}
|
||||
|
||||
return {
|
||||
toString: function () {
|
||||
var str = '';
|
||||
var addr = readWord(0xca); // Start
|
||||
var himem = readWord(0x4c);
|
||||
do {
|
||||
/*var len = */readByte(addr++);
|
||||
var lineno = readWord(addr);
|
||||
addr += 2;
|
||||
|
||||
str += lineno;
|
||||
str += ' ';
|
||||
var val = 0;
|
||||
do {
|
||||
val = readByte(addr++);
|
||||
if (val >= 0xB0 && val <= 0xB9) {
|
||||
str += readWord(addr);
|
||||
addr += 2;
|
||||
}
|
||||
else if (val < 0x80 && val > 0x01) {
|
||||
var t = TOKENS[val];
|
||||
if (t.length > 1)
|
||||
str += ' ';
|
||||
str += t;
|
||||
if (t.length > 1)
|
||||
str += ' ';
|
||||
}
|
||||
else if (val > 0x80)
|
||||
str += LETTERS[val - 0x80];
|
||||
} while (val != 0x01);
|
||||
str += '\n';
|
||||
} while (addr < himem);
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
}
|
188
js/intbasic/decompiler.ts
Normal file
188
js/intbasic/decompiler.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import { byte, Memory, word } from 'js/types';
|
||||
|
||||
const LETTERS =
|
||||
' ' +
|
||||
' !"#$%&\'()*+,-./0123456789:;<=>?' +
|
||||
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_' +
|
||||
'`abcdefghijklmnopqrstuvwxyz{|}~ ';
|
||||
|
||||
const TOKENS: Record<byte, string> = {
|
||||
0x02: '_',
|
||||
0x03: ':',
|
||||
0x04: 'LOAD',
|
||||
0x05: 'SAVE',
|
||||
0x06: 'CON',
|
||||
0x07: 'RUN',
|
||||
0x08: 'RUN',
|
||||
0x09: 'DEL',
|
||||
0x0A: ',',
|
||||
0x0B: 'NEW',
|
||||
0x0C: 'CLR',
|
||||
0x0D: 'AUTO',
|
||||
0x0E: ',',
|
||||
0x0F: 'MAN',
|
||||
0x10: 'HIMEM:',
|
||||
0x11: 'LOMEM:',
|
||||
0x12: '+',
|
||||
0x13: '-',
|
||||
0x14: '*',
|
||||
0x15: '/',
|
||||
0x16: '=',
|
||||
0x17: '#',
|
||||
0x18: '>=',
|
||||
0x19: '>',
|
||||
0x1A: '<=',
|
||||
0x1B: '<>',
|
||||
0x1C: '<',
|
||||
0x1D: 'AND',
|
||||
0x1E: 'OR',
|
||||
0x1F: 'MOD',
|
||||
0x20: '^',
|
||||
0x21: '+',
|
||||
0x22: '(',
|
||||
0x23: ',',
|
||||
0x24: 'THEN',
|
||||
0x25: 'THEN',
|
||||
0x26: ',',
|
||||
0x27: ',',
|
||||
0x28: '\'',
|
||||
0x29: '\'',
|
||||
0x2A: '(',
|
||||
0x2B: '!',
|
||||
0x2C: '!',
|
||||
0x2D: '(',
|
||||
0x2E: 'PEEK',
|
||||
0x2F: 'RND',
|
||||
0x30: 'SGN',
|
||||
0x31: 'ABS',
|
||||
0x32: 'PDL',
|
||||
0x33: 'RNDX',
|
||||
0x34: '(',
|
||||
0x35: '+',
|
||||
0x36: '-',
|
||||
0x37: 'NOT',
|
||||
0x38: '(',
|
||||
0x39: '=',
|
||||
0x3A: '#',
|
||||
0x3B: 'LEN(',
|
||||
0x3C: 'ASC(',
|
||||
0x3D: 'SCRN(',
|
||||
0x3E: ',',
|
||||
0x3F: '(',
|
||||
0x40: '$',
|
||||
0x41: '$',
|
||||
0x42: '(',
|
||||
0x43: ',',
|
||||
0x44: ',',
|
||||
0x45: ';',
|
||||
0x46: ';',
|
||||
0x47: ';',
|
||||
0x48: ',',
|
||||
0x49: ',',
|
||||
0x4A: ',',
|
||||
0x4B: 'TEXT',
|
||||
0x4C: 'GR',
|
||||
0x4D: 'CALL',
|
||||
0x4E: 'DIM',
|
||||
0x4F: 'DIM',
|
||||
0x50: 'TAB',
|
||||
0x51: 'END',
|
||||
0x52: 'INPUT',
|
||||
0x53: 'INPUT',
|
||||
0x54: 'INPUT',
|
||||
0x55: 'FOR',
|
||||
0x56: '=',
|
||||
0x57: 'TO',
|
||||
0x58: 'STEP',
|
||||
0x59: 'NEXT',
|
||||
0x5A: ',',
|
||||
0x5B: 'RETURN',
|
||||
0x5C: 'GOSUB',
|
||||
0x5D: 'REM',
|
||||
0x5E: 'LET',
|
||||
0x5F: 'GOTO',
|
||||
0x60: 'IF',
|
||||
0x61: 'PRINT',
|
||||
0x62: 'PRINT',
|
||||
0x63: 'PRINT',
|
||||
0x64: 'POKE',
|
||||
0x65: ',',
|
||||
0x66: 'COLOR=',
|
||||
0x67: 'PLOT',
|
||||
0x68: ',',
|
||||
0x69: 'HLIN',
|
||||
0x6A: ',',
|
||||
0x6B: 'AT',
|
||||
0x6C: 'VLIN',
|
||||
0x6E: 'AT',
|
||||
0x6F: 'VTAB',
|
||||
0x70: '=',
|
||||
0x71: '=',
|
||||
0x72: ')',
|
||||
0x73: ')',
|
||||
0x74: 'LIST',
|
||||
0x76: 'LIST',
|
||||
0x77: 'POP',
|
||||
0x78: 'NODSP',
|
||||
0x79: 'NODSP',
|
||||
0x7A: 'NOTRACE',
|
||||
0x7B: 'DSP',
|
||||
0x7C: 'DSP',
|
||||
0x7D: 'TRACE',
|
||||
0x7E: 'PR#',
|
||||
0x7F: 'IN#'
|
||||
};
|
||||
|
||||
export default class IntBasicDump
|
||||
{
|
||||
constructor(private mem: Memory) {}
|
||||
|
||||
private readByte(addr: word) {
|
||||
const page = addr >> 8,
|
||||
off = addr & 0xff;
|
||||
|
||||
return this.mem.read(page, off);
|
||||
}
|
||||
|
||||
private readWord(addr: word) {
|
||||
const lsb = this.readByte(addr);
|
||||
const msb = this.readByte(addr + 1);
|
||||
|
||||
return (msb << 8) | lsb;
|
||||
}
|
||||
|
||||
toString () {
|
||||
let str = '';
|
||||
let addr = this.readWord(0xca); // Start
|
||||
const himem = this.readWord(0x4c);
|
||||
do {
|
||||
/*var len = */this.readByte(addr++);
|
||||
const lineno = this.readWord(addr);
|
||||
addr += 2;
|
||||
|
||||
str += lineno;
|
||||
str += ' ';
|
||||
let val = 0;
|
||||
do {
|
||||
val = this.readByte(addr++);
|
||||
if (val >= 0xB0 && val <= 0xB9) {
|
||||
str += this.readWord(addr);
|
||||
addr += 2;
|
||||
}
|
||||
else if (val < 0x80 && val > 0x01) {
|
||||
const t = TOKENS[val];
|
||||
if (t.length > 1)
|
||||
str += ' ';
|
||||
str += t;
|
||||
if (t.length > 1)
|
||||
str += ' ';
|
||||
}
|
||||
else if (val > 0x80)
|
||||
str += LETTERS[val - 0x80];
|
||||
} while (val != 0x01);
|
||||
str += '\n';
|
||||
} while (addr < himem);
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user