Compare commits

...

2 Commits

Author SHA1 Message Date
442253b953 Add more inforation to directory entry 2025-01-15 15:07:23 -05:00
cfa42a5cb9 Decode root directory entries 2025-01-15 11:16:21 -05:00
2 changed files with 181 additions and 7 deletions

View File

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

View File

@ -30,6 +30,38 @@ export interface IBiosParameterBlock {
totalSectorsLarge: number
}
export interface IAttributes {
readOnly: boolean
hidden: boolean
system: boolean
volumeId: boolean
directory: boolean
archive: boolean
}
export interface IStandardDirEntry {
type: 'standard-entry'
name: string
extension: string
attributes: IAttributes
size: number
firstCluster: number
}
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 +70,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)
}
}
function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null {
if (buffer.byteLength < 512) {
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(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 +158,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 +182,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 +220,72 @@ 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),
)
const attributeByte = data.getUint8(11)
const attributes: IAttributes = {
readOnly: (attributeByte & 0x01) !== 0,
hidden: (attributeByte & 0x02) !== 0,
system: (attributeByte & 0x04) !== 0,
volumeId: (attributeByte & 0x08) !== 0,
directory: (attributeByte & 0x10) !== 0,
archive: (attributeByte & 0x20) !== 0,
}
const firstCluster = (data.getUint16(20, true) << 16) | data.getUint16(26, true)
const size = data.getUint32(28, true)
return {
type: 'standard-entry',
name,
extension,
attributes,
firstCluster,
size,
}
}
}
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
}