Sub directories

This commit is contained in:
Jordan Goulder 2025-01-17 14:43:09 -05:00
parent a6503ae65b
commit 5fdbd4ceb1
2 changed files with 65 additions and 60 deletions

View File

@ -13,11 +13,22 @@ 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> <h3>Directory Entries</h3>
<pre>{{ floppyDisk.rootDirEntries }}</pre> <pre>{{ floppyDisk.rootDirEntries }}</pre>
<h3>Data</h3>
<pre>Offset: {{ floppyDisk.dataOffset }}</pre>
</section> </section>
</template> </template>
<style scoped></style> <style scoped>
pre {
width: 100%;
padding: 0.5em 1em 10em;
box-sizing: border-box;
overflow: scroll;
min-height: 10rem;
height: 20rem;
max-height: 20rem;
background: rgba(255, 255, 255, 0.15);
color: #c0c0c0;
border: 1px solid rgba(255, 255, 255, 0.3);
}
</style>

View File

@ -48,6 +48,7 @@ export interface IStandardDirEntry {
firstCluster: number firstCluster: number
clusterChain: number[] clusterChain: number[]
data: ArrayBuffer data: ArrayBuffer
subDirEntries: TDirEntry[]
} }
export interface ILongFileNameDirEntry { export interface ILongFileNameDirEntry {
@ -62,17 +63,17 @@ export interface IUnusedDirEntry {
type: 'unused-entry' type: 'unused-entry'
} }
export type DirEntry = IStandardDirEntry | ILongFileNameDirEntry | IFinalDirEntry | IUnusedDirEntry export type TDirEntry = IStandardDirEntry | ILongFileNameDirEntry | IFinalDirEntry | IUnusedDirEntry
export class FloppyDisk { export class FloppyDisk {
private readonly _buffer = new ArrayBuffer(0) buffer = new ArrayBuffer(0)
private _bootSector: IBootSectorInfo | null = null private _bootSector: IBootSectorInfo | null = null
constructor(buffer: ArrayBuffer) { constructor(buffer: ArrayBuffer) {
this._buffer = buffer this.buffer = buffer
} }
private _rootDirEntries: DirEntry[] | null = null private _rootDirEntries: TDirEntry[] | null = null
get rootDirEntries() { get rootDirEntries() {
if (this._rootDirEntries === null) { if (this._rootDirEntries === null) {
@ -81,59 +82,23 @@ export class FloppyDisk {
const end = offset + size const end = offset + size
if (this._buffer.byteLength >= end) { if (this.buffer.byteLength >= end) {
this._rootDirEntries = decodeDirectoryEntries( this._rootDirEntries = decodeDirectoryEntries(
new DataView(this._buffer, this.rootDirOffset, this.rootDirSize), new DataView(this.buffer, this.rootDirOffset, this.rootDirSize),
this,
) )
} }
if (this._rootDirEntries === null) {
return null
}
for (const entry of this._rootDirEntries) {
if (entry.type === 'standard-entry' && (entry.size > 0 || entry.attributes.directory)) {
entry.clusterChain = this.clusterChain(entry.firstCluster)
const dataSize = entry.clusterChain.length * this.bytesPerCluster
if (dataSize === 0) {
continue
}
const data = new Uint8Array(dataSize)
for (let i = 0; i < entry.clusterChain.length; i++) {
const offset = (entry.clusterChain[i] - 2) * this.bytesPerCluster
const view = new Uint8Array(
this._buffer,
this.dataOffset + offset,
this.bytesPerCluster,
)
data.set(view, i * this.bytesPerCluster)
}
console.log(
entry.name.trim() + (entry.extension.trim() ? '.' + entry.extension.trim() : ''),
)
entry.data = data.slice(0, entry.size > 0 ? entry.size : data.byteLength)
if (
!entry.attributes.directory &&
(['BAT', 'TXT', 'BAS'].includes(entry.extension) || entry.name.trim() === 'LICENSE')
) {
const decoder = new TextDecoder()
console.log(decoder.decode(entry.data))
}
}
}
} }
return this._rootDirEntries return this._rootDirEntries
} }
get bootSectorInfo() { get bootSectorInfo() {
if (this._buffer.byteLength < 512) { if (this.buffer.byteLength < 512) {
return null return null
} }
return (this._bootSector = return (this._bootSector =
this._bootSector ?? decodeBootSector(new DataView(this._buffer, 0, 512))) this._bootSector ?? decodeBootSector(new DataView(this.buffer, 0, 512)))
} }
get tableSize() { get tableSize() {
@ -198,7 +163,7 @@ export class FloppyDisk {
const entityOffset = tableOffset % this.bytesPerSector const entityOffset = tableOffset % this.bytesPerSector
const table = new DataView( const table = new DataView(
this._buffer, this.buffer,
tableSector * this.bytesPerSector, tableSector * this.bytesPerSector,
2 * this.bytesPerSector, 2 * this.bytesPerSector,
) )
@ -297,7 +262,7 @@ function decodeBootSector(data: DataView): IBootSectorInfo | null {
} }
} }
function decodeDirectoryEntry(data: DataView): DirEntry | null { function decodeDirectoryEntry(data: DataView, fd: FloppyDisk): TDirEntry | null {
if (data.byteLength < 32) { if (data.byteLength < 32) {
return null return null
} }
@ -313,10 +278,10 @@ function decodeDirectoryEntry(data: DataView): DirEntry | null {
return { type: 'long-filename-entry' } return { type: 'long-filename-entry' }
} else { } else {
const asciiDecoder = new TextDecoder('ascii') const asciiDecoder = new TextDecoder('ascii')
const name = asciiDecoder.decode(data.buffer.slice(data.byteOffset, data.byteOffset + 8)) const name = asciiDecoder.decode(data.buffer.slice(data.byteOffset, data.byteOffset + 8)).trim()
const extension = asciiDecoder.decode( const extension = asciiDecoder
data.buffer.slice(data.byteOffset + 8, data.byteOffset + 11), .decode(data.buffer.slice(data.byteOffset + 8, data.byteOffset + 11))
) .trim()
const attributeByte = data.getUint8(11) const attributeByte = data.getUint8(11)
@ -332,28 +297,57 @@ function decodeDirectoryEntry(data: DataView): DirEntry | null {
const firstCluster = (data.getUint16(20, true) << 16) | data.getUint16(26, true) const firstCluster = (data.getUint16(20, true) << 16) | data.getUint16(26, true)
const size = data.getUint32(28, true) const size = data.getUint32(28, true)
return { const entry: IStandardDirEntry = {
type: 'standard-entry', type: 'standard-entry',
name, name,
extension, extension,
attributes, attributes,
firstCluster, firstCluster,
clusterChain: [], clusterChain: [],
data: new ArrayBuffer(0), data: new Uint8Array(0),
size, size,
subDirEntries: [],
} }
if (
entry.size > 0 ||
(entry.attributes.directory && entry.name !== '.' && entry.name !== '..')
) {
entry.clusterChain = fd.clusterChain(entry.firstCluster)
const dataSize = entry.clusterChain.length * fd.bytesPerCluster
if (dataSize !== 0) {
const entryData = new Uint8Array(dataSize)
for (let i = 0; i < entry.clusterChain.length; i++) {
const offset = (entry.clusterChain[i] - 2) * fd.bytesPerCluster
const view = new Uint8Array(fd.buffer, fd.dataOffset + offset, fd.bytesPerCluster)
entryData.set(view, i * fd.bytesPerCluster)
}
const resizedData = entryData.slice(0, entry.size > 0 ? entry.size : entryData.byteLength)
if (entry.attributes.directory) {
entry.subDirEntries = decodeDirectoryEntries(new DataView(resizedData.buffer), fd)
} else {
entry.data = resizedData
}
}
}
return entry
} }
} }
function decodeDirectoryEntries(data: DataView): DirEntry[] { function decodeDirectoryEntries(data: DataView, fd: FloppyDisk): TDirEntry[] {
const entries: DirEntry[] = [] const entries: TDirEntry[] = []
let offset = 0 let offset = 0
while (data.byteLength - offset >= 32) { while (data.byteLength - offset >= 32) {
const dirData = new DataView(data.buffer, data.byteOffset + offset, 32) const dirData = new DataView(data.buffer, data.byteOffset + offset, 32)
offset += 32 offset += 32
const entry = decodeDirectoryEntry(dirData) const entry = decodeDirectoryEntry(dirData, fd)
if (entry === null) { if (entry === null) {
break break
} }