diff --git a/src/floppy/disk.ts b/src/floppy/disk.ts index 6bc0269..4f156a7 100644 --- a/src/floppy/disk.ts +++ b/src/floppy/disk.ts @@ -46,6 +46,8 @@ export interface IStandardDirEntry { attributes: IAttributes size: number firstCluster: number + clusterChain: number[] + data: ArrayBuffer } export interface ILongFileNameDirEntry { @@ -84,7 +86,45 @@ export class FloppyDisk { new DataView(this._buffer, this.rootDirOffset, this.rootDirSize), ) } + + 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 } @@ -116,6 +156,14 @@ export class FloppyDisk { return this.bootSectorInfo?.biosParameterBlock?.bytesPerSector ?? 0 } + get sectorsPerCluster() { + return this.bootSectorInfo?.biosParameterBlock?.sectorsPerCluster ?? 0 + } + + get bytesPerCluster() { + return this.bytesPerSector * this.sectorsPerCluster + } + get rootDirSectors() { if (this.bytesPerSector === 0) { return 0 @@ -143,6 +191,34 @@ export class FloppyDisk { get rootDirSize() { return this.rootDirSectors * this.bytesPerSector } + + nextCluster(current: number): number { + const tableOffset = Math.floor(current + current / 2) + const tableSector = Math.floor(this.reservedSectorCount + tableOffset / this.bytesPerSector) + const entityOffset = tableOffset % this.bytesPerSector + + const table = new DataView( + this._buffer, + tableSector * this.bytesPerSector, + 2 * this.bytesPerSector, + ) + + const value = table.getUint16(entityOffset, true) + + return current & 1 ? value >> 4 : value & 0x0fff + } + + clusterChain(start: number): number[] { + const chain = [start] + + let next = this.nextCluster(start) + while (next > 1 && next < 0xff7) { + chain.push(next) + next = this.nextCluster(next) + } + + return chain + } } function decodeBootSector(data: DataView): IBootSectorInfo | null { @@ -262,6 +338,8 @@ function decodeDirectoryEntry(data: DataView): DirEntry | null { extension, attributes, firstCluster, + clusterChain: [], + data: new ArrayBuffer(0), size, } }