apple2js/js/formats/prodos/file_entry.ts

288 lines
8.4 KiB
TypeScript

import {
dateToUint32,
readFileName,
writeFileName,
uint32ToDate,
} from './utils';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
import type { byte, word } from 'js/types';
import { toHex } from 'js/util';
import { ProDOSVolume } from '.';
import { VDH } from './vdh';
import { Directory } from './directory';
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';
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;
constructor(public volume: ProDOSVolume) {}
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
);
}
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;
}
}
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; ) {
const fileEntry = new FileEntry(volume);
fileEntry.read(block, offset);
entries.push(fileEntry);
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
idx++;
}
offset += header.entryLength;
count++;
if (count > header.entriesPerBlock) {
block = new DataView(blocks[next].buffer);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 1;
}
}
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);
}
}