Compare commits
2 Commits
e6bdde292d
...
442253b953
| Author | SHA1 | Date | |
|---|---|---|---|
| 442253b953 | |||
| cfa42a5cb9 |
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,38 @@ 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
|
||||||
@ -38,18 +70,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeBootSector(buffer: ArrayBuffer): IBootSectorInfo | null {
|
get firstDataSector() {
|
||||||
if (buffer.byteLength < 512) {
|
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
|
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 +158,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 +182,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 +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