Decode root directory entries

This commit is contained in:
Jordan Goulder 2025-01-15 11:16:21 -05:00
parent e6bdde292d
commit cfa42a5cb9
2 changed files with 151 additions and 7 deletions

View File

@ -13,6 +13,10 @@ const floppyDisk = computed(() => {
<section v-if="floppyDisk.bootSectorInfo"> <section v-if="floppyDisk.bootSectorInfo">
<h3>Boot Sector</h3> <h3>Boot Sector</h3>
<pre>{{ floppyDisk.bootSectorInfo }}</pre> <pre>{{ floppyDisk.bootSectorInfo }}</pre>
<h3>Root Dir</h3>
<pre>{{ floppyDisk.rootDirEntries }}</pre>
<h3>Data</h3>
<pre>Offset: {{ floppyDisk.dataOffset }}</pre>
</section> </section>
</template> </template>

View File

@ -30,6 +30,26 @@ export interface IBiosParameterBlock {
totalSectorsLarge: number 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 { export class FloppyDisk {
private readonly _buffer = new ArrayBuffer(0) private readonly _buffer = new ArrayBuffer(0)
private _bootSector: IBootSectorInfo | null = null private _bootSector: IBootSectorInfo | null = null
@ -38,18 +58,87 @@ export class FloppyDisk {
this._buffer = buffer 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() { 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 { function decodeBootSector(data: DataView): IBootSectorInfo | null {
if (buffer.byteLength < 512) { if (data.byteLength < 512) {
return null return null
} }
const asciiDecoder = new TextDecoder('ascii') const asciiDecoder = new TextDecoder('ascii')
const data = new DataView(buffer)
const jumpInstruction: [number, number, number] = [ const jumpInstruction: [number, number, number] = [
data.getUint8(0), data.getUint8(0),
@ -57,7 +146,7 @@ function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null {
data.getUint8(2), 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 bytesPerSector = data.getUint16(11, true)
const sectorsPerCluster = data.getUint8(13) const sectorsPerCluster = data.getUint8(13)
const reservedSectorCount = data.getUint16(14, true) const reservedSectorCount = data.getUint16(14, true)
@ -81,12 +170,12 @@ function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null {
let volumeLabel let volumeLabel
if (bootSignature === 0x29) { if (bootSignature === 0x29) {
volumeLabel = asciiDecoder.decode(buffer.slice(43, 54)) volumeLabel = asciiDecoder.decode(data.buffer.slice(43, 54))
} }
let fileSystemType let fileSystemType
if (bootSignature === 0x29) { if (bootSignature === 0x29) {
fileSystemType = asciiDecoder.decode(buffer.slice(54, 62)) fileSystemType = asciiDecoder.decode(data.buffer.slice(54, 62))
} }
const bootPartitionSignature = data.getUint16(510, true) 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
}