-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactoring: Move ISO9660 filesystem code to a separate file
- Loading branch information
Showing
5 changed files
with
179 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
// Copyright (c) 2019 Kichikuou <[email protected]> | ||
// This source code is governed by the MIT License, see the LICENSE file. | ||
import type { MainModule as XSystem35Module } from './xsystem35.js'; | ||
import { ald_getdata, isMobileSafari, createBlob, loadScript, createWaveFile } from './util.js'; | ||
import { ald_getdata, isMobileSafari, createBlob, loadScript } from './util.js'; | ||
import { createWaveFile } from './cdimage.js'; | ||
|
||
export interface CDDALoaderSource { | ||
extractTrack(track: number): Promise<Blob>; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,150 +1,5 @@ | ||
// Copyright (c) 2017 Kichikuou <[email protected]> | ||
// This source code is governed by the MIT License, see the LICENSE file. | ||
import { createWaveFile } from './util.js'; | ||
|
||
export class ISO9660FileSystem { | ||
private decoder: TextDecoder; | ||
|
||
static async create(sectorReader: Reader): Promise<ISO9660FileSystem> { | ||
let best_vd: VolumeDescriptor | null = null; | ||
for (let sector = 0x10;; sector++) { | ||
let vd = new VolumeDescriptor(await sectorReader.readSector(sector)); | ||
switch (vd.type) { | ||
case VDType.Primary: | ||
if (!best_vd) | ||
best_vd = vd; | ||
break; | ||
case VDType.Supplementary: | ||
if (vd.encoding()) | ||
best_vd = vd; | ||
break; | ||
case VDType.Terminator: | ||
if (!best_vd) | ||
throw new Error('PVD not found'); | ||
return new ISO9660FileSystem(sectorReader, best_vd); | ||
} | ||
} | ||
} | ||
|
||
private constructor(private sectorReader: Reader, private vd: VolumeDescriptor) { | ||
this.decoder = new TextDecoder(vd.encoding()); | ||
} | ||
|
||
volumeLabel(): string { | ||
return this.vd.volumeLabel(this.decoder); | ||
} | ||
|
||
rootDir(): DirEnt { | ||
return this.vd.rootDirEnt(this.decoder); | ||
} | ||
|
||
async getDirEnt(name: string, parent: DirEnt): Promise<DirEnt | null> { | ||
name = name.toLowerCase(); | ||
for (let e of await this.readDir(parent)) { | ||
if (e.name.toLowerCase() === name) | ||
return e; | ||
} | ||
return null; | ||
} | ||
|
||
async readDir(dirent: DirEnt): Promise<DirEnt[]> { | ||
let sector = dirent.sector; | ||
let position = 0; | ||
let length = dirent.size; | ||
let entries: DirEnt[] = []; | ||
let buf: ArrayBuffer; | ||
while (position < length) { | ||
if (position === 0) | ||
buf = await this.sectorReader.readSector(sector); | ||
let child = new DirEnt(buf!, position, this.decoder); | ||
if (child.length === 0) { | ||
// Padded end of sector | ||
position = 2048; | ||
} else { | ||
entries.push(child); | ||
position += child.length; | ||
} | ||
if (position > 2048) | ||
throw new Error('dirent across sector boundary'); | ||
if (position === 2048) { | ||
sector++; | ||
position = 0; | ||
length -= 2048; | ||
} | ||
} | ||
return entries; | ||
} | ||
|
||
readFile(dirent: DirEnt): Promise<Uint8Array[]> { | ||
return this.sectorReader.readSequentialSectors(dirent.sector, dirent.size); | ||
} | ||
} | ||
|
||
enum VDType { | ||
Primary = 1, | ||
Supplementary = 2, | ||
Terminator = 255 | ||
} | ||
|
||
class VolumeDescriptor { | ||
private view: DataView; | ||
constructor(private buf: ArrayBuffer) { | ||
this.view = new DataView(buf); | ||
if (ASCIIArrayToString(new Uint8Array(this.buf, 1, 5)) !== 'CD001') | ||
throw new Error('Not a valid CD image'); | ||
} | ||
get type(): number { | ||
return this.view.getUint8(0); | ||
} | ||
volumeLabel(decoder: TextDecoder): string { | ||
return decoder.decode(new DataView(this.buf, 40, 32)).trim(); | ||
} | ||
encoding(): string | undefined { | ||
if (this.type === VDType.Primary) | ||
return 'shift_jis'; | ||
if (this.escapeSequence().match(/%\/[@CE]/)) | ||
return 'utf-16be'; // Joliet | ||
return undefined; | ||
} | ||
escapeSequence(): string { | ||
return ASCIIArrayToString(new Uint8Array(this.buf, 88, 32)).trim(); | ||
} | ||
rootDirEnt(decoder: TextDecoder): DirEnt { | ||
return new DirEnt(this.buf, 156, decoder); | ||
} | ||
} | ||
|
||
export class DirEnt { | ||
private view: DataView; | ||
constructor(private buf: ArrayBuffer, private offset: number, private decoder: TextDecoder) { | ||
this.view = new DataView(buf, offset); | ||
} | ||
get length(): number { | ||
return this.view.getUint8(0); | ||
} | ||
get sector(): number { | ||
return this.view.getUint32(2, true); | ||
} | ||
get size(): number { | ||
return this.view.getUint32(10, true); | ||
} | ||
get isDirectory(): boolean { | ||
return (this.view.getUint8(25) & 2) !== 0; | ||
} | ||
get name(): string { | ||
let len = this.view.getUint8(32); | ||
let name = new DataView(this.buf, this.offset + 33, len) | ||
if (len === 1) { | ||
switch (name.getUint8(0)) { | ||
case 0: | ||
return '.'; | ||
case 1: | ||
return '..'; | ||
} | ||
} | ||
return this.decoder.decode(name).split(';')[0]; | ||
} | ||
} | ||
|
||
export interface Reader { | ||
readSector(sector: number): Promise<ArrayBuffer>; | ||
|
@@ -153,7 +8,7 @@ export interface Reader { | |
extractTrack(track: number): Promise<Blob>; | ||
} | ||
|
||
export async function createReader(img: File, metadata?: File) { | ||
export async function createReader(img: File, metadata?: File): Promise<Reader> { | ||
if (img.name.toLowerCase().endsWith('.iso')) { | ||
return new IsoReader(img); | ||
} else if (!metadata) { | ||
|
@@ -369,3 +224,23 @@ class MdfMdsReader implements Reader { | |
function ASCIIArrayToString(buffer: Uint8Array): string { | ||
return String.fromCharCode.apply(null, buffer as any); | ||
} | ||
|
||
export function createWaveFile(sampleRate: number, channels: number, dataSize: number, chunks: BlobPart[]): Blob { | ||
let headerBuf = new ArrayBuffer(44); | ||
let header = new DataView(headerBuf); | ||
header.setUint32(0, 0x52494646, false); // 'RIFF' | ||
header.setUint32(4, dataSize + 36, true); // filesize - 8 | ||
header.setUint32(8, 0x57415645, false); // 'WAVE' | ||
header.setUint32(12, 0x666D7420, false); // 'fmt ' | ||
header.setUint32(16, 16, true); // size of fmt chunk | ||
header.setUint16(20, 1, true); // PCM format | ||
header.setUint16(22, channels, true); // stereo | ||
header.setUint32(24, sampleRate, true); // sampling rate | ||
header.setUint32(28, sampleRate * channels * 2, true); // bytes/sec | ||
header.setUint16(32, channels * 2, true); // block size | ||
header.setUint16(34, 16, true); // bit/sample | ||
header.setUint32(36, 0x64617461, false); // 'data' | ||
header.setUint32(40, dataSize, true); // data size | ||
chunks.unshift(headerBuf); | ||
return new Blob(chunks, { type: 'audio/wav' }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Copyright (c) 2024 Kichikuou <[email protected]> | ||
// This source code is governed by the MIT License, see the LICENSE file. | ||
import { Reader } from './cdimage.js'; | ||
|
||
export class FileSystem { | ||
private decoder: TextDecoder; | ||
|
||
static async create(sectorReader: Reader): Promise<FileSystem> { | ||
let best_vd: VolumeDescriptor | null = null; | ||
for (let sector = 0x10;; sector++) { | ||
let vd = new VolumeDescriptor(await sectorReader.readSector(sector)); | ||
switch (vd.type) { | ||
case VDType.Primary: | ||
if (!best_vd) | ||
best_vd = vd; | ||
break; | ||
case VDType.Supplementary: | ||
if (vd.encoding()) | ||
best_vd = vd; | ||
break; | ||
case VDType.Terminator: | ||
if (!best_vd) | ||
throw new Error('PVD not found'); | ||
return new FileSystem(sectorReader, best_vd); | ||
} | ||
} | ||
} | ||
|
||
private constructor(private sectorReader: Reader, private vd: VolumeDescriptor) { | ||
this.decoder = new TextDecoder(vd.encoding()); | ||
} | ||
|
||
volumeLabel(): string { | ||
return this.vd.volumeLabel(this.decoder); | ||
} | ||
|
||
rootDir(): DirEnt { | ||
return this.vd.rootDirEnt(this.decoder); | ||
} | ||
|
||
async getDirEnt(name: string, parent: DirEnt): Promise<DirEnt | null> { | ||
name = name.toLowerCase(); | ||
for (let e of await this.readDir(parent)) { | ||
if (e.name.toLowerCase() === name) | ||
return e; | ||
} | ||
return null; | ||
} | ||
|
||
async readDir(dirent: DirEnt): Promise<DirEnt[]> { | ||
let sector = dirent.sector; | ||
let position = 0; | ||
let length = dirent.size; | ||
let entries: DirEnt[] = []; | ||
let buf: ArrayBuffer; | ||
while (position < length) { | ||
if (position === 0) | ||
buf = await this.sectorReader.readSector(sector); | ||
let child = new DirEnt(buf!, position, this.decoder); | ||
if (child.length === 0) { | ||
// Padded end of sector | ||
position = 2048; | ||
} else { | ||
entries.push(child); | ||
position += child.length; | ||
} | ||
if (position > 2048) | ||
throw new Error('dirent across sector boundary'); | ||
if (position === 2048) { | ||
sector++; | ||
position = 0; | ||
length -= 2048; | ||
} | ||
} | ||
return entries; | ||
} | ||
|
||
readFile(dirent: DirEnt): Promise<Uint8Array[]> { | ||
return this.sectorReader.readSequentialSectors(dirent.sector, dirent.size); | ||
} | ||
} | ||
|
||
enum VDType { | ||
Primary = 1, | ||
Supplementary = 2, | ||
Terminator = 255 | ||
} | ||
|
||
class VolumeDescriptor { | ||
private view: DataView; | ||
constructor(private buf: ArrayBuffer) { | ||
this.view = new DataView(buf); | ||
if (ASCIIArrayToString(new Uint8Array(this.buf, 1, 5)) !== 'CD001') | ||
throw new Error('Not a valid CD image'); | ||
} | ||
get type(): number { | ||
return this.view.getUint8(0); | ||
} | ||
volumeLabel(decoder: TextDecoder): string { | ||
return decoder.decode(new DataView(this.buf, 40, 32)).trim(); | ||
} | ||
encoding(): string | undefined { | ||
if (this.type === VDType.Primary) | ||
return 'shift_jis'; | ||
if (this.escapeSequence().match(/%\/[@CE]/)) | ||
return 'utf-16be'; // Joliet | ||
return undefined; | ||
} | ||
escapeSequence(): string { | ||
return ASCIIArrayToString(new Uint8Array(this.buf, 88, 32)).trim(); | ||
} | ||
rootDirEnt(decoder: TextDecoder): DirEnt { | ||
return new DirEnt(this.buf, 156, decoder); | ||
} | ||
} | ||
|
||
export class DirEnt { | ||
private view: DataView; | ||
constructor(private buf: ArrayBuffer, private offset: number, private decoder: TextDecoder) { | ||
this.view = new DataView(buf, offset); | ||
} | ||
get length(): number { | ||
return this.view.getUint8(0); | ||
} | ||
get sector(): number { | ||
return this.view.getUint32(2, true); | ||
} | ||
get size(): number { | ||
return this.view.getUint32(10, true); | ||
} | ||
get isDirectory(): boolean { | ||
return (this.view.getUint8(25) & 2) !== 0; | ||
} | ||
get name(): string { | ||
let len = this.view.getUint8(32); | ||
let name = new DataView(this.buf, this.offset + 33, len) | ||
if (len === 1) { | ||
switch (name.getUint8(0)) { | ||
case 0: | ||
return '.'; | ||
case 1: | ||
return '..'; | ||
} | ||
} | ||
return this.decoder.decode(name).split(';')[0]; | ||
} | ||
} | ||
|
||
function ASCIIArrayToString(buffer: Uint8Array): string { | ||
return String.fromCharCode.apply(null, buffer as any); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.