diff --git a/src/components/DiskInfo.vue b/src/components/DiskInfo.vue index c6dc491..5f7605c 100644 --- a/src/components/DiskInfo.vue +++ b/src/components/DiskInfo.vue @@ -13,6 +13,10 @@ const floppyDisk = computed(() => {

Boot Sector

{{ floppyDisk.bootSectorInfo }}
+

Root Dir

+
{{ floppyDisk.rootDirEntries }}
+

Data

+
Offset: {{ floppyDisk.dataOffset }}
diff --git a/src/floppy/disk.ts b/src/floppy/disk.ts index 7e32417..a91cfde 100644 --- a/src/floppy/disk.ts +++ b/src/floppy/disk.ts @@ -30,6 +30,26 @@ export interface IBiosParameterBlock { totalSectorsLarge: number } +export interface IStandardDirEntry { + type: 'standard-entry' + name: string + extension: string +} + +export interface ILongFileNameDirEntry { + type: 'long-filename-entry' +} + +export interface IFinalDirEntry { + type: 'final-entry' +} + +export interface IUnusedDirEntry { + type: 'unused-entry' +} + +export type DirEntry = IStandardDirEntry | ILongFileNameDirEntry | IFinalDirEntry | IUnusedDirEntry + export class FloppyDisk { private readonly _buffer = new ArrayBuffer(0) private _bootSector: IBootSectorInfo | null = null @@ -38,18 +58,87 @@ export class FloppyDisk { this._buffer = buffer } + private _rootDirEntries: DirEntry[] | null = null + + get rootDirEntries() { + if (this._rootDirEntries === null) { + const offset = this.rootDirOffset + const size = this.rootDirSize + + const end = offset + size + + if (this._buffer.byteLength >= end) { + this._rootDirEntries = decodeDirectoryEntries( + new DataView(this._buffer, this.rootDirOffset, this.rootDirSize), + ) + } + } + return this._rootDirEntries + } + get bootSectorInfo() { - return (this._bootSector = this._bootSector ?? decodeBootSector(this._buffer)) + if (this._buffer.byteLength < 512) { + return null + } + return (this._bootSector = + this._bootSector ?? decodeBootSector(new DataView(this._buffer, 0, 512))) + } + + get tableSize() { + return this.bootSectorInfo?.biosParameterBlock?.sectorsPerTableSmall ?? 0 + } + + get rootEntryCount() { + return this.bootSectorInfo?.biosParameterBlock?.rootEntryCount ?? 0 + } + + get tableCount() { + return this.bootSectorInfo?.biosParameterBlock?.tableCount ?? 0 + } + + get reservedSectorCount() { + return this.bootSectorInfo?.biosParameterBlock?.reservedSectorCount ?? 0 + } + + get bytesPerSector() { + return this.bootSectorInfo?.biosParameterBlock?.bytesPerSector ?? 0 + } + + get rootDirSectors() { + if (this.bytesPerSector === 0) { + return 0 + } else { + return Math.ceil((this.rootEntryCount * 32) / this.bytesPerSector) + } + } + + get firstDataSector() { + return this.reservedSectorCount + this.tableCount * this.tableSize + this.rootDirSectors + } + + get dataOffset() { + return this.firstDataSector * this.bytesPerSector + } + + get firstRootDirSector() { + return this.firstDataSector - this.rootDirSectors + } + + get rootDirOffset() { + return this.firstRootDirSector * this.bytesPerSector + } + + get rootDirSize() { + return this.rootDirSectors * this.bytesPerSector } } -function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null { - if (buffer.byteLength < 512) { +function decodeBootSector(data: DataView): IBootSectorInfo | null { + if (data.byteLength < 512) { return null } const asciiDecoder = new TextDecoder('ascii') - const data = new DataView(buffer) const jumpInstruction: [number, number, number] = [ data.getUint8(0), @@ -57,7 +146,7 @@ function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null { data.getUint8(2), ] - const oemName = asciiDecoder.decode(buffer.slice(3, 11)) + const oemName = asciiDecoder.decode(data.buffer.slice(3, 11)) const bytesPerSector = data.getUint16(11, true) const sectorsPerCluster = data.getUint8(13) const reservedSectorCount = data.getUint16(14, true) @@ -81,12 +170,12 @@ function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null { let volumeLabel if (bootSignature === 0x29) { - volumeLabel = asciiDecoder.decode(buffer.slice(43, 54)) + volumeLabel = asciiDecoder.decode(data.buffer.slice(43, 54)) } let fileSystemType if (bootSignature === 0x29) { - fileSystemType = asciiDecoder.decode(buffer.slice(54, 62)) + fileSystemType = asciiDecoder.decode(data.buffer.slice(54, 62)) } const bootPartitionSignature = data.getUint16(510, true) @@ -119,3 +208,54 @@ function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null { }, } } + +function decodeDirectoryEntry(data: DataView): DirEntry | null { + if (data.byteLength < 32) { + return null + } + + const firstByte = data.getUint8(0) + const attributeByte = data.getUint8(11) + + if (firstByte === 0) { + return { type: 'final-entry' } + } else if (firstByte === 0xe5) { + return { type: 'unused-entry' } + } else if (attributeByte === 0x0f) { + 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), + ) + return { + type: 'standard-entry', + name, + extension, + } + } +} + +function decodeDirectoryEntries(data: DataView): DirEntry[] { + const entries: DirEntry[] = [] + + let offset = 0 + while (data.byteLength - offset >= 32) { + const dirData = new DataView(data.buffer, data.byteOffset + offset, 32) + offset += 32 + + const entry = decodeDirectoryEntry(dirData) + if (entry === null) { + break + } + + entries.push(entry) + + if (entry.type === 'final-entry') { + break + } + } + + return entries +}