Compare commits
No commits in common. "442253b953e78b21e652d81273d7caf8dab8c225" and "e6bdde292d9d5e90fe34b3b00aea83b47e2f14a5" have entirely different histories.
442253b953
...
e6bdde292d
@ -13,10 +13,6 @@ 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>
|
||||||
|
|
||||||
|
|||||||
@ -30,38 +30,6 @@ export interface IBiosParameterBlock {
|
|||||||
totalSectorsLarge: number
|
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 {
|
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
|
||||||
@ -70,87 +38,18 @@ 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() {
|
||||||
if (this._buffer.byteLength < 512) {
|
return (this._bootSector = this._bootSector ?? decodeBootSector(this._buffer))
|
||||||
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() {
|
function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null {
|
||||||
return this.reservedSectorCount + this.tableCount * this.tableSize + this.rootDirSectors
|
if (buffer.byteLength < 512) {
|
||||||
}
|
|
||||||
|
|
||||||
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
|
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),
|
||||||
@ -158,7 +57,7 @@ function decodeBootSector(data: DataView): IBootSectorInfo | null {
|
|||||||
data.getUint8(2),
|
data.getUint8(2),
|
||||||
]
|
]
|
||||||
|
|
||||||
const oemName = asciiDecoder.decode(data.buffer.slice(3, 11))
|
const oemName = asciiDecoder.decode(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)
|
||||||
@ -182,12 +81,12 @@ function decodeBootSector(data: DataView): IBootSectorInfo | null {
|
|||||||
|
|
||||||
let volumeLabel
|
let volumeLabel
|
||||||
if (bootSignature === 0x29) {
|
if (bootSignature === 0x29) {
|
||||||
volumeLabel = asciiDecoder.decode(data.buffer.slice(43, 54))
|
volumeLabel = asciiDecoder.decode(buffer.slice(43, 54))
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileSystemType
|
let fileSystemType
|
||||||
if (bootSignature === 0x29) {
|
if (bootSignature === 0x29) {
|
||||||
fileSystemType = asciiDecoder.decode(data.buffer.slice(54, 62))
|
fileSystemType = asciiDecoder.decode(buffer.slice(54, 62))
|
||||||
}
|
}
|
||||||
|
|
||||||
const bootPartitionSignature = data.getUint16(510, true)
|
const bootPartitionSignature = data.getUint16(510, true)
|
||||||
@ -220,72 +119,3 @@ function decodeBootSector(data: DataView): 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