2021-12-22 18:37:21 +00:00
|
|
|
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
|
|
|
|
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
|
|
|
|
import type { byte, word } from 'js/types';
|
2022-07-23 19:00:38 +00:00
|
|
|
import { toHex } from 'js/util';
|
2021-12-22 18:37:21 +00:00
|
|
|
import { ProDOSVolume } from '.';
|
|
|
|
import { VDH } from './vdh';
|
|
|
|
import { Directory } from './directory';
|
2022-07-23 19:00:38 +00:00
|
|
|
import { ProDOSFile } from './base_file';
|
|
|
|
import { SaplingFile } from './sapling_file';
|
|
|
|
import { SeedlingFile } from './seedling_file';
|
|
|
|
import { TreeFile } from './tree_file';
|
|
|
|
import ApplesoftDump from 'js/applesoft/decompiler';
|
2021-12-22 18:37:21 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-07-23 19:00:38 +00:00
|
|
|
constructor(public volume: ProDOSVolume) { }
|
|
|
|
|
2021-12-22 18:37:21 +00:00
|
|
|
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);
|
|
|
|
}
|
2022-07-23 19:00:38 +00:00
|
|
|
|
|
|
|
getFileData() {
|
|
|
|
let file: ProDOSFile | null = null;
|
|
|
|
|
|
|
|
switch (this.storageType) {
|
|
|
|
case STORAGE_TYPES.SEEDLING:
|
|
|
|
file = new SeedlingFile(this.volume, this);
|
|
|
|
break;
|
|
|
|
case STORAGE_TYPES.SAPLING:
|
|
|
|
file = new SaplingFile(this.volume, this);
|
|
|
|
break;
|
|
|
|
case STORAGE_TYPES.TREE:
|
|
|
|
file = new TreeFile(this.volume, this);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file) {
|
|
|
|
return file.read();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getFileText() {
|
|
|
|
const data = this.getFileData();
|
|
|
|
let result: string | null = null;
|
|
|
|
let address = 0;
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
if (this.fileType === 0xFC) { // BAS
|
|
|
|
result = new ApplesoftDump(data, 0).decompile();
|
|
|
|
} else {
|
|
|
|
if (this.fileType === 0x06) { // BIN
|
|
|
|
address = this.auxType;
|
|
|
|
}
|
|
|
|
result = '';
|
|
|
|
let hex = '';
|
|
|
|
let ascii = '';
|
|
|
|
for (let idx = 0; idx < data.length; idx++) {
|
|
|
|
const val = data[idx];
|
|
|
|
if (idx % 16 === 0) {
|
|
|
|
if (idx !== 0) {
|
|
|
|
result += `${hex} ${ascii}\n`;
|
|
|
|
}
|
|
|
|
hex = '';
|
|
|
|
ascii = '';
|
|
|
|
result += `${toHex(address + idx, 4)}:`;
|
|
|
|
}
|
|
|
|
hex += ` ${toHex(val)}`;
|
|
|
|
ascii += (val & 0x7f) >= 0x20 ? String.fromCharCode(val & 0x7f) : '.';
|
|
|
|
}
|
|
|
|
result += '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2021-12-22 18:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-07-23 19:00:38 +00:00
|
|
|
for (let idx = 0; idx < header.fileCount;) {
|
|
|
|
const fileEntry = new FileEntry(volume);
|
2021-12-22 18:37:21 +00:00
|
|
|
fileEntry.read(block, offset);
|
|
|
|
entries.push(fileEntry);
|
2022-07-23 19:00:38 +00:00
|
|
|
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
|
|
|
|
idx++;
|
|
|
|
}
|
2021-12-22 18:37:21 +00:00
|
|
|
offset += header.entryLength;
|
|
|
|
count++;
|
2022-07-23 19:00:38 +00:00
|
|
|
if (count > header.entriesPerBlock) {
|
2021-12-22 18:37:21 +00:00
|
|
|
block = new DataView(blocks[next].buffer);
|
|
|
|
next = block.getUint16(0x02, true);
|
|
|
|
offset = 0x4;
|
2022-07-23 19:00:38 +00:00
|
|
|
count = 1;
|
2021-12-22 18:37:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|