Compare commits
2 Commits
e6bdde292d
...
442253b953
| Author | SHA1 | Date | |
|---|---|---|---|
| 442253b953 | |||
| cfa42a5cb9 |
@ -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>
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
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 +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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user