From 5fdbd4ceb1fdf51f8f408bf99376a011c248fe47 Mon Sep 17 00:00:00 2001 From: Jordan Goulder Date: Fri, 17 Jan 2025 14:43:09 -0500 Subject: [PATCH] Sub directories --- src/components/DiskInfo.vue | 19 +++++-- src/floppy/disk.ts | 106 +++++++++++++++++------------------- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/components/DiskInfo.vue b/src/components/DiskInfo.vue index 5f7605c..9bfd6b4 100644 --- a/src/components/DiskInfo.vue +++ b/src/components/DiskInfo.vue @@ -13,11 +13,22 @@ const floppyDisk = computed(() => {

Boot Sector

{{ floppyDisk.bootSectorInfo }}
-

Root Dir

+

Directory Entries

{{ floppyDisk.rootDirEntries }}
-

Data

-
Offset: {{ floppyDisk.dataOffset }}
- + diff --git a/src/floppy/disk.ts b/src/floppy/disk.ts index 4f156a7..04c9b04 100644 --- a/src/floppy/disk.ts +++ b/src/floppy/disk.ts @@ -48,6 +48,7 @@ export interface IStandardDirEntry { firstCluster: number clusterChain: number[] data: ArrayBuffer + subDirEntries: TDirEntry[] } export interface ILongFileNameDirEntry { @@ -62,17 +63,17 @@ export interface IUnusedDirEntry { type: 'unused-entry' } -export type DirEntry = IStandardDirEntry | ILongFileNameDirEntry | IFinalDirEntry | IUnusedDirEntry +export type TDirEntry = IStandardDirEntry | ILongFileNameDirEntry | IFinalDirEntry | IUnusedDirEntry export class FloppyDisk { - private readonly _buffer = new ArrayBuffer(0) + buffer = new ArrayBuffer(0) private _bootSector: IBootSectorInfo | null = null constructor(buffer: ArrayBuffer) { - this._buffer = buffer + this.buffer = buffer } - private _rootDirEntries: DirEntry[] | null = null + private _rootDirEntries: TDirEntry[] | null = null get rootDirEntries() { if (this._rootDirEntries === null) { @@ -81,59 +82,23 @@ export class FloppyDisk { const end = offset + size - if (this._buffer.byteLength >= end) { + if (this.buffer.byteLength >= end) { this._rootDirEntries = decodeDirectoryEntries( - new DataView(this._buffer, this.rootDirOffset, this.rootDirSize), + new DataView(this.buffer, this.rootDirOffset, this.rootDirSize), + this, ) } - - if (this._rootDirEntries === null) { - return null - } - - for (const entry of this._rootDirEntries) { - if (entry.type === 'standard-entry' && (entry.size > 0 || entry.attributes.directory)) { - entry.clusterChain = this.clusterChain(entry.firstCluster) - - const dataSize = entry.clusterChain.length * this.bytesPerCluster - if (dataSize === 0) { - continue - } - - const data = new Uint8Array(dataSize) - for (let i = 0; i < entry.clusterChain.length; i++) { - const offset = (entry.clusterChain[i] - 2) * this.bytesPerCluster - const view = new Uint8Array( - this._buffer, - this.dataOffset + offset, - this.bytesPerCluster, - ) - data.set(view, i * this.bytesPerCluster) - } - console.log( - entry.name.trim() + (entry.extension.trim() ? '.' + entry.extension.trim() : ''), - ) - entry.data = data.slice(0, entry.size > 0 ? entry.size : data.byteLength) - if ( - !entry.attributes.directory && - (['BAT', 'TXT', 'BAS'].includes(entry.extension) || entry.name.trim() === 'LICENSE') - ) { - const decoder = new TextDecoder() - console.log(decoder.decode(entry.data)) - } - } - } } return this._rootDirEntries } get bootSectorInfo() { - if (this._buffer.byteLength < 512) { + if (this.buffer.byteLength < 512) { return null } return (this._bootSector = - this._bootSector ?? decodeBootSector(new DataView(this._buffer, 0, 512))) + this._bootSector ?? decodeBootSector(new DataView(this.buffer, 0, 512))) } get tableSize() { @@ -198,7 +163,7 @@ export class FloppyDisk { const entityOffset = tableOffset % this.bytesPerSector const table = new DataView( - this._buffer, + this.buffer, tableSector * this.bytesPerSector, 2 * this.bytesPerSector, ) @@ -297,7 +262,7 @@ function decodeBootSector(data: DataView): IBootSectorInfo | null { } } -function decodeDirectoryEntry(data: DataView): DirEntry | null { +function decodeDirectoryEntry(data: DataView, fd: FloppyDisk): TDirEntry | null { if (data.byteLength < 32) { return null } @@ -313,10 +278,10 @@ function decodeDirectoryEntry(data: DataView): DirEntry | null { return { type: 'long-filename-entry' } } else { const asciiDecoder = new TextDecoder('ascii') - const name = asciiDecoder.decode(data.buffer.slice(data.byteOffset, data.byteOffset + 8)) - const extension = asciiDecoder.decode( - data.buffer.slice(data.byteOffset + 8, data.byteOffset + 11), - ) + const name = asciiDecoder.decode(data.buffer.slice(data.byteOffset, data.byteOffset + 8)).trim() + const extension = asciiDecoder + .decode(data.buffer.slice(data.byteOffset + 8, data.byteOffset + 11)) + .trim() const attributeByte = data.getUint8(11) @@ -332,28 +297,57 @@ function decodeDirectoryEntry(data: DataView): DirEntry | null { const firstCluster = (data.getUint16(20, true) << 16) | data.getUint16(26, true) const size = data.getUint32(28, true) - return { + const entry: IStandardDirEntry = { type: 'standard-entry', name, extension, attributes, firstCluster, clusterChain: [], - data: new ArrayBuffer(0), + data: new Uint8Array(0), size, + subDirEntries: [], } + + if ( + entry.size > 0 || + (entry.attributes.directory && entry.name !== '.' && entry.name !== '..') + ) { + entry.clusterChain = fd.clusterChain(entry.firstCluster) + + const dataSize = entry.clusterChain.length * fd.bytesPerCluster + + if (dataSize !== 0) { + const entryData = new Uint8Array(dataSize) + for (let i = 0; i < entry.clusterChain.length; i++) { + const offset = (entry.clusterChain[i] - 2) * fd.bytesPerCluster + const view = new Uint8Array(fd.buffer, fd.dataOffset + offset, fd.bytesPerCluster) + entryData.set(view, i * fd.bytesPerCluster) + } + + const resizedData = entryData.slice(0, entry.size > 0 ? entry.size : entryData.byteLength) + + if (entry.attributes.directory) { + entry.subDirEntries = decodeDirectoryEntries(new DataView(resizedData.buffer), fd) + } else { + entry.data = resizedData + } + } + } + + return entry } } -function decodeDirectoryEntries(data: DataView): DirEntry[] { - const entries: DirEntry[] = [] +function decodeDirectoryEntries(data: DataView, fd: FloppyDisk): TDirEntry[] { + const entries: TDirEntry[] = [] let offset = 0 while (data.byteLength - offset >= 32) { const dirData = new DataView(data.buffer, data.byteOffset + offset, 32) offset += 32 - const entry = decodeDirectoryEntry(dirData) + const entry = decodeDirectoryEntry(dirData, fd) if (entry === null) { break }