Compare commits

...

4 Commits

Author SHA1 Message Date
Ian Flanigan 7cbb027ab1
Merge 9d4a265daf into eab7de7db3 2023-10-30 15:45:55 +08:00
dependabot[bot] eab7de7db3
Bump @babel/traverse from 7.17.10 to 7.23.2 (#200)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.17.10 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 06:56:16 -07:00
dependabot[bot] f4c5a0e024
Bump postcss from 8.4.25 to 8.4.31 (#199)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.25 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.25...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 07:23:50 -07:00
Ian Flanigan 9d4a265daf Refactor storage types
Today, lots of information about how a file or JSON becomes a disk
image is embedded in the metadata for the image and/or disk. This
makes it hard to write back to the source when the in-memory disk
image changes.

This refactoring is an attempt to break out all of the bits of logic
into composable pieces.  While this is mestly concerned with reading
right now, the idea is that it will eventually allow configuring
writing as well.  The main goal is to allow round-tripping to the same
file on disk, but, in theory, it could also save to a different file
or the local database, too.

Note that this is a work in progress.
2022-06-05 23:30:34 +02:00
7 changed files with 1013 additions and 193 deletions

View File

@ -1,7 +1,5 @@
import { explodeSector16, PO } from './format_utils';
import { bytify } from '../util';
import type { byte } from '../types';
import { NibbleDisk, DiskOptions, ENCODING_NIBBLE } from './types';
import { NibbleDisk, DiskOptions, ENCODING_NIBBLE, TrackSectorSource } from './types';
import { ByteArrayArrayTrackSectorSource, ByteArrayByteSource, ByteTrackSectorSource, ProdosOrderedTrackSectorSource, TrackSector6x2NibbleTrackSource } from './sources';
/**
* Returns a `Disk` object from ProDOS-ordered image data.
@ -19,24 +17,23 @@ export default function createDiskFromProDOS(options: DiskOptions) {
readOnly: readOnly || false,
};
for (let physical_track = 0; physical_track < 35; physical_track++) {
let track: byte[] = [];
for (let physical_sector = 0; physical_sector < 16; physical_sector++) {
const prodos_sector = PO[physical_sector];
let sector;
if (rawData) {
const off = (16 * physical_track + prodos_sector) * 256;
sector = new Uint8Array(rawData.slice(off, off + 256));
} else if (data) {
sector = data[physical_track][prodos_sector];
} else {
throw new Error('Requires data or rawData');
}
track = track.concat(
explodeSector16(volume, physical_track, physical_sector, sector)
);
}
disk.tracks[physical_track] = bytify(track);
let trackSectorSource: TrackSectorSource;
if (rawData) {
trackSectorSource =
new ByteTrackSectorSource(
new ByteArrayByteSource(new Uint8Array(rawData)));
} else if (data) {
trackSectorSource = new ByteArrayArrayTrackSectorSource(data);
} else {
throw new Error('Requires data or rawData');
}
const nibbleTrackSource =
new TrackSector6x2NibbleTrackSource(
new ProdosOrderedTrackSectorSource(trackSectorSource), volume);
for (let physical_track = 0; physical_track < nibbleTrackSource.numTracks(); physical_track++) {
disk.tracks[physical_track] = nibbleTrackSource.read(physical_track);
}
return disk;

148
js/formats/sources.ts Normal file
View File

@ -0,0 +1,148 @@
import { byte } from 'js/types';
import { concat } from 'js/util';
import { D13O, DO, explodeSector13, explodeSector16, PO } from './format_utils';
import { ByteSource, Metadata, MetadataSource, NibbleTrackSource, TrackSectorSource } from './types';
export class FileHandleFileSource {
constructor(private readonly fileHandle: FileSystemFileHandle) { }
getFile(): Promise<File> {
return this.fileHandle.getFile();
}
}
export class FileHandleMetadataSource implements MetadataSource {
constructor(private readonly fileHandle: FileSystemFileHandle) { }
getMetadata(): Metadata {
return {
name: this.fileHandle.name,
};
}
}
export class FileByteArraySource {
constructor(private readonly file: File) { }
async getBytes() {
return new Uint8Array(await this.file.arrayBuffer());
}
}
export class ByteArrayByteSource implements ByteSource {
constructor(private readonly byteArray: Uint8Array) { }
read(offset: number, length: number): Uint8Array {
return this.byteArray.subarray(offset, offset + length);
}
length(): number {
return this.byteArray.length;
}
}
export class ByteArrayArrayTrackSectorSource implements TrackSectorSource {
constructor(
protected readonly data: Uint8Array[][]) { }
read(track: number, sector: number): Uint8Array {
return this.data[track][sector];
}
numTracks(): number {
return this.data.length;
}
}
export class ByteTrackSectorSource implements TrackSectorSource {
constructor(
protected readonly byteSource: ByteSource,
protected readonly sectors = 16) { }
read(track: number, sector: number): Uint8Array {
return this.byteSource.read((track * this.sectors + sector) * 256, 256);
}
numTracks(): number {
let tracks = this.byteSource.length() / (this.sectors * 256);
if (tracks !== Math.floor(tracks)) {
tracks = Math.floor(tracks + 1);
}
return tracks;
}
}
export class ProdosOrderedTrackSectorSource implements TrackSectorSource {
constructor(private readonly trackSectorSource: TrackSectorSource) { }
read(track: number, sector: number): Uint8Array {
return this.trackSectorSource.read(track, PO[sector]);
}
numTracks(): number {
return this.trackSectorSource.numTracks();
}
}
export class DosOrderedTrackSectorSource implements TrackSectorSource {
constructor(private readonly trackSectorSource: TrackSectorSource) { }
read(track: number, sector: number): Uint8Array {
return this.trackSectorSource.read(track, DO[sector]);
}
numTracks(): number {
return this.trackSectorSource.numTracks();
}
}
export class TrackSector6x2NibbleTrackSource implements NibbleTrackSource {
constructor(
private readonly trackSectorSource: TrackSectorSource,
private readonly volume: byte = 254) {
}
read(track: number): Uint8Array {
const sectors: byte[][] = [];
for (let sector = 0; sector < 16; sector++) {
const data = this.trackSectorSource.read(track, sector);
sectors.push(explodeSector16(this.volume, track, sector, data));
}
return concat(...sectors);
}
numTracks(): number {
return this.trackSectorSource.numTracks();
}
}
export class TrackSector5x3NibbleTrackSource implements NibbleTrackSource {
constructor(
private readonly trackSectorSource: TrackSectorSource,
private readonly volume: byte = 254) {
}
/*
* DOS 13-sector disks have the physical sectors skewed on the track. The skew
* between physical sectors is 10 (A), resulting in the following physical order:
*
* 0 A 7 4 1 B 8 5 2 C 9 6 3
*
* Note that because physical sector == logical sector, this works slightly
* differently from the DOS and ProDOS nibblizers.
*/
read(track: number): Uint8Array {
const sectors: byte[][] = [];
for (let sector = 0; sector < 13; sector++) {
const physical_sector = D13O[sector];
const data = this.trackSectorSource.read(track, physical_sector);
sectors.push(explodeSector13(this.volume, track, physical_sector, data));
}
return concat(...sectors);
}
numTracks(): number {
return this.trackSectorSource.numTracks();
}
}

View File

@ -1,4 +1,4 @@
import type { byte, memory, MemberOf, word } from '../types';
import type { byte, memory, MemberOf, word, ReadonlyUint8Array } from '../types';
import type { GamepadConfiguration } from '../ui/types';
import { InfoChunk } from './woz';
@ -8,6 +8,55 @@ export type SupportedSectors = MemberOf<typeof SUPPORTED_SECTORS>;
export const DRIVE_NUMBERS = [1, 2] as const;
export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
export interface Metadata {
name: string;
side?: string;
}
export interface MetadataSource {
getMetadata(): Metadata;
}
export interface ByteSource {
read(offset: number, length: number): Uint8Array;
length(): number;
}
export interface ByteSink {
write(offset: number, data: ReadonlyUint8Array): void;
}
export interface JsonSource {
read(): string;
}
export interface TrackSectorSource {
/** Returns the logical sector data for the given physical sector. */
read(track: byte, sector: byte): Uint8Array;
numTracks(): byte;
}
export interface TrackSectorSink {
write(track: byte, sector: byte, data: ReadonlyUint8Array): void;
}
export interface NibbleTrackSource {
read(track: byte): Uint8Array;
numTracks(): byte;
}
export interface NibbleTrackSink {
write(track: byte, data: Uint8Array): void;
}
export interface BlockSource {
read(block: word): Uint8Array;
}
export interface BlockSink {
write(block: word, data: Uint8Array): void;
}
/**
* Arguments for the disk format processors.
*/

330
package-lock.json generated
View File

@ -67,12 +67,13 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.16.7"
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"engines": {
"node": ">=6.9.0"
@ -116,13 +117,14 @@
}
},
"node_modules/@babel/generator": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz",
"integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"dependencies": {
"@babel/types": "^7.17.10",
"@jridgewell/gen-mapping": "^0.1.0",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
@ -220,13 +222,10 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
"integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.16.7"
},
"engines": {
"node": ">=6.9.0"
}
@ -241,25 +240,25 @@
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz",
"integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"dependencies": {
"@babel/template": "^7.16.7",
"@babel/types": "^7.17.0"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
"integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@ -372,21 +371,30 @@
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
"integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dev": true,
"dependencies": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -425,13 +433,13 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz",
"integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"engines": {
@ -439,9 +447,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz",
"integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@ -1416,33 +1424,33 @@
}
},
"node_modules/@babel/template": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
"integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/parser": "^7.16.7",
"@babel/types": "^7.16.7"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz",
"integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.10",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.17.9",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.17.10",
"@babel/types": "^7.17.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@ -1451,12 +1459,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz",
"integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.16.7",
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
@ -2374,13 +2383,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.0",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
@ -2414,20 +2424,6 @@
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
@ -4724,7 +4720,7 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/color-support": {
@ -5612,7 +5608,7 @@
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
@ -6925,7 +6921,7 @@
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
@ -11179,9 +11175,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
"integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@ -14460,12 +14456,13 @@
},
"dependencies": {
"@babel/code-frame": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"requires": {
"@babel/highlight": "^7.16.7"
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
}
},
"@babel/compat-data": {
@ -14499,13 +14496,14 @@
}
},
"@babel/generator": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz",
"integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"requires": {
"@babel/types": "^7.17.10",
"@jridgewell/gen-mapping": "^0.1.0",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
@ -14582,13 +14580,10 @@
}
},
"@babel/helper-environment-visitor": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
"integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
"dev": true,
"requires": {
"@babel/types": "^7.16.7"
}
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true
},
"@babel/helper-explode-assignable-expression": {
"version": "7.13.0",
@ -14600,22 +14595,22 @@
}
},
"@babel/helper-function-name": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz",
"integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"requires": {
"@babel/template": "^7.16.7",
"@babel/types": "^7.17.0"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
}
},
"@babel/helper-hoist-variables": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
"integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dev": true,
"requires": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.22.5"
}
},
"@babel/helper-member-expression-to-functions": {
@ -14710,18 +14705,24 @@
}
},
"@babel/helper-split-export-declaration": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
"integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dev": true,
"requires": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.22.5"
}
},
"@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true
},
"@babel/helper-validator-option": {
@ -14754,20 +14755,20 @@
}
},
"@babel/highlight": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz",
"integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz",
"integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true
},
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
@ -15517,41 +15518,42 @@
}
},
"@babel/template": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
"integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
"@babel/parser": "^7.16.7",
"@babel/types": "^7.16.7"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
}
},
"@babel/traverse": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz",
"integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.10",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.17.9",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.17.10",
"@babel/types": "^7.17.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.17.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz",
"integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
},
@ -16217,13 +16219,14 @@
}
},
"@jridgewell/gen-mapping": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.0",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
@ -16246,19 +16249,6 @@
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
},
"dependencies": {
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
}
}
},
"@jridgewell/sourcemap-codec": {
@ -18068,7 +18058,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"color-support": {
@ -18748,7 +18738,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true
},
"escodegen": {
@ -19721,7 +19711,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"has-property-descriptors": {
@ -22857,9 +22847,9 @@
}
},
"postcss": {
"version": "8.4.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
"integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.6",

View File

@ -0,0 +1,68 @@
/* eslint-disable @typescript-eslint/no-namespace */
import '';
interface CustomMatchers<R = unknown> {
equalsUint8Array(other: Uint8Array): R;
}
declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
function short(o: { toString(): string }): string {
const result = o.toString();
return result.length > 8 ? result.substring(0, 5) + '...' : result;
}
function smallDiff(a: Uint8Array, b: Uint8Array): string {
let result = '';
if (!(a instanceof Uint8Array)) {
result += `${short(a)} is not a Uint8Array`;
}
if (!(b instanceof Uint8Array)) {
result += `${short(b)} is not a Uint8Array`;
}
if (result.length) {
return result;
}
if (a.length !== b.length) {
return `${short(a)} is not the same length as ${short(b)}: ${a.length} !== ${b.length}`;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
result += `first diff at ${i}:\n`;
result += ` ${a.subarray(i, Math.min(i + 5, a.length)).toString()}\n`;
result += ` ${b.subarray(i, Math.min(i + 5, b.length)).toString()}`;
return result;
}
}
return 'no differences found';
}
expect.extend({
/**
* Jest matcher for large Uint8Arrays
*/
equalsUint8Array(received: Uint8Array, other: Uint8Array) {
const pass = received.length === other.length && received.every((value, i) => other[i] === value);
if (pass) {
return {
message: () => 'expected arrays not to be equal',
pass: true,
};
} else {
return {
message: () => `expected arrays to be equal: ${smallDiff(received, other)}`,
pass: false,
};
}
}
});

View File

@ -0,0 +1,545 @@
import './matchers';
import { ByteArrayByteSource, ProdosOrderedTrackSectorSource, ByteTrackSectorSource, TrackSector5x3NibbleTrackSource, TrackSector6x2NibbleTrackSource, DosOrderedTrackSectorSource } from 'js/formats/sources';
import { BYTES_BY_SECTOR_IMAGE, BYTES_BY_TRACK_IMAGE } from './testdata/16sector';
import { BYTES_BY_SECTOR_IMAGE as S13_BYTES_BY_SECTOR_IMAGE, BYTES_BY_TRACK_IMAGE as S13_BYTES_BY_TRACK_IMAGE } from './testdata/13sector';
import { D13O, DO, PO } from 'js/formats/format_utils';
import { expectSequence, findBytes, skipGap } from './util';
describe('ProdosOrderedTrackSectorSource', () => {
it('returns the correct logical sector for each physical sector', () => {
const byteSource = new ByteArrayByteSource(BYTES_BY_SECTOR_IMAGE);
const byteTrackSectorSource = new ByteTrackSectorSource(byteSource);
const prodosTrackSectorSource = new ProdosOrderedTrackSectorSource(byteTrackSectorSource);
for (let s = 0; s < 16; s++) {
const sector = prodosTrackSectorSource.read(0, s);
const expected = new Uint8Array(256).fill(PO[s]);
expect(sector).equalsUint8Array(expected);
}
});
});
describe('DosOrderedByteTrackSectorSource', () => {
it('returns the correct logical sector for each physical sector', () => {
const byteSource = new ByteArrayByteSource(BYTES_BY_SECTOR_IMAGE);
const byteTrackSectorSource = new ByteTrackSectorSource(byteSource);
const dosTrackSectorSource = new DosOrderedTrackSectorSource(byteTrackSectorSource);
for (let s = 0; s < 16; s++) {
const sector = dosTrackSectorSource.read(0, s);
const expected = new Uint8Array(256).fill(DO[s]);
expect(sector).equalsUint8Array(expected);
}
});
});
describe('TrackSector6x2NibbleTrackSource', () => {
const nibbleTrackSource = (image: Uint8Array = BYTES_BY_TRACK_IMAGE) => {
const byteSource = new ByteArrayByteSource(image);
const byteTrackSectorSource = new ByteTrackSectorSource(byteSource);
const prodosTrackSectorSource = new ProdosOrderedTrackSectorSource(byteTrackSectorSource);
return new TrackSector6x2NibbleTrackSource(prodosTrackSectorSource, 10);
};
it('has correct number of tracks', () => {
const trackSectorNibbleTrackSource = nibbleTrackSource();
expect(trackSectorNibbleTrackSource.numTracks()).toBe(35);
});
it('has correct number of bytes in all tracks', () => {
// Track 0 is slightly longer for some reason.
const trackSectorNibbleTrackSource = nibbleTrackSource();
expect(trackSectorNibbleTrackSource.read(0).length).toEqual(6632);
for (let i = 1; i < trackSectorNibbleTrackSource.numTracks(); i++) {
expect(trackSectorNibbleTrackSource.read(i).length).toEqual(6602);
}
});
it('has correct GAP 1', () => {
// From Beneith Apple DOS, GAP 1 should have 12-85 0xFF bytes
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
let numFF = 0;
while (track[numFF] === 0xFF && numFF < 0x100) {
numFF++;
}
expect(numFF).toBeGreaterThanOrEqual(40);
expect(numFF).toBeLessThanOrEqual(128);
});
it('has correct Address Field for track 0, sector 0', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
let i = skipGap(track);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0x96]);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// sector 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// checksum = 0b00000101
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 0, sector 0 (BYTES_BY_TRACK)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
// skip to the first address epilogue
let i = findBytes(track, [0xDE, 0xAA, 0xEB]);
expect(i).toBeGreaterThan(50);
i = skipGap(track, i);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xAD]);
// data (all zeros, which is 0x96 with 6 and 2 encoding)
for (let j = 0; j < 342; j++) {
expect(track[i++]).toBe(0x96);
}
// checksum (also zero)
expect(track[i++]).toBe(0x96);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Field for track 0, sector 1', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
// first sector prologue
let i = findBytes(track, [0xD5, 0xAA, 0x96]);
// second sector prologue
i = findBytes(track, [0xD5, 0xAA, 0x96], i);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// sector 1 = 0b00000001
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101011);
// checksum = 0b00000101
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101011);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 0, sector 1 (BYTES_BY_SECTOR)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource(BYTES_BY_SECTOR_IMAGE);
const track = trackSectorNibbleTrackSource.read(0);
// First data field prologue
let i = findBytes(track, [0xD5, 0xAA, 0xAD]);
// Second data field prologue
i = findBytes(track, [0xD5, 0xAA, 0xAD], i);
// Sector 1 is ProDOS sector 8.
// In 6 x 2 encoding, the lowest 2 bits of all the bytes come first.
// 0x07 is 0b00001000, so the lowest two bits are 0b00, reversed and
// repeated would be 0b000000 (00 -> 0x96). Even though each byte is
// XOR'd with the previous, they are all the same. This means there
// are 86 0b00000000 (00 -> 0x96) bytes.
for (let j = 0; j < 86; j++) {
expect(track[i++]).toBe(0x96);
}
// Next we get 256 instances of the top bits, 0b000010. Again, with
// the XOR, this means one 0b000010 XOR 0b000000 = 0b000010
// (02 -> 0x9A) followed by 255 0b0000000 (00 -> 0x96).
expect(track[i++]).toBe(0x9A);
for (let j = 0; j < 255; j++) {
expect(track[i++]).toBe(0x96);
}
// checksum 0b000010 XOR 0b000000 -> 9A
expect(track[i++]).toBe(0x9A);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Field for track 1, sector 0', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(1);
let i = skipGap(track);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0x96]);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 1 = 0b00000001
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101011);
// sector 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// checksum = 0b00000100
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101011);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 1, sector 0 (BYTES_BY_TRACK)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource(BYTES_BY_TRACK_IMAGE);
const track = trackSectorNibbleTrackSource.read(1);
let i = findBytes(track, [0xDE, 0xAA, 0xEB]);
expect(i).toBeGreaterThan(50);
i = skipGap(track, i);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xAD]);
// In 6 x 2 encoding, the lowest 2 bits of all the bytes come first.
// This would normally mean 86 instances of 0b101010 (2A -> 0xE6),
// but each byte is XOR'd with the previous. Since all of the bits
// are the same, this means there are 85 0b000000 (00 -> 0x96).
expect(track[i++]).toBe(0xE6);
for (let j = 0; j < 85; j++) {
expect(track[i++]).toBe(0x96);
}
// Next we get 256 instances of the top bits, 0b000000. Again, with
// the XOR, this means one 0x101010 (2A -> 0xE6) followed by 255
// 0b0000000 (00 -> 0x96).
expect(track[i++]).toBe(0xE6);
for (let j = 0; j < 255; j++) {
expect(track[i++]).toBe(0x96);
}
// checksum (also zero)
expect(track[i++]).toBe(0x96);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Fields for all tracks', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource(BYTES_BY_TRACK_IMAGE);
for (let t = 0; t < trackSectorNibbleTrackSource.numTracks(); t++) {
// We essentially seek through the track for the Address Fields
const track = trackSectorNibbleTrackSource.read(t);
let i = findBytes(track, [0xD5, 0xAA, 0x96]);
for (let s = 0; s <= 15; s++) {
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// convert track to 4x4 encoding
const track4x4XX = ((t & 0b10101010) >> 1) | 0b10101010;
const track4x4YY = (t & 0b01010101) | 0b10101010;
expect(track[i++]).toBe(track4x4XX);
expect(track[i++]).toBe(track4x4YY);
// convert sector to 4x4 encoding
const sector4x4XX = ((s & 0b10101010) >> 1) | 0b10101010;
const sector4x4YY = (s & 0b01010101) | 0b10101010;
expect(track[i++]).toBe(sector4x4XX);
expect(track[i++]).toBe(sector4x4YY);
// checksum
expect(track[i++]).toBe(0b10101111 ^ track4x4XX ^ sector4x4XX);
expect(track[i++]).toBe(0b10101010 ^ track4x4YY ^ sector4x4YY);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
// next sector
i = findBytes(track, [0xD5, 0xAA, 0x96], i);
}
}
});
});
describe('TrackSector5x3NibbleTrackSource', () => {
const nibbleTrackSource = (image: Uint8Array = S13_BYTES_BY_TRACK_IMAGE) => {
const byteSource = new ByteArrayByteSource(image);
const dos32TrackSectorSource = new ByteTrackSectorSource(byteSource, 13);
return new TrackSector5x3NibbleTrackSource(dos32TrackSectorSource, 10);
};
it('has correct number of tracks', () => {
const trackSectorNibbleTrackSource = nibbleTrackSource();
expect(trackSectorNibbleTrackSource.numTracks()).toBe(35);
});
it('has correct number of bytes in all tracks', () => {
// Track 0 is slightly longer for some reason.
const trackSectorNibbleTrackSource = nibbleTrackSource();
expect(trackSectorNibbleTrackSource.read(0).length).toEqual(6289);
for (let i = 1; i < trackSectorNibbleTrackSource.numTracks(); i++) {
expect(trackSectorNibbleTrackSource.read(i).length).toEqual(6265);
}
});
it('has correct GAP 1', () => {
// From Beneith Apple DOS, GAP 1 should have 12-85 0xFF bytes
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
let numFF = 0;
while (track[numFF] === 0xFF && numFF < 0x100) {
numFF++;
}
expect(numFF).toBeGreaterThanOrEqual(40);
expect(numFF).toBeLessThanOrEqual(128);
});
// eslint-disable-next-line jest/no-identical-title
it('has correct GAP 1', () => {
// From Beneith Apple DOS, GAP 1 should have 12-85 0xFF bytes
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
let numFF = 0;
while (track[numFF] === 0xFF && numFF < 0x100) {
numFF++;
}
expect(numFF).toBeGreaterThanOrEqual(40);
expect(numFF).toBeLessThanOrEqual(128);
});
it('has correct Address Field for track 0, sector 0', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(0);
let i = skipGap(track);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xB5]);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// sector 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// checksum = 0b00000101
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 0, sector 0 (BYTES_BY_TRACK)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource(S13_BYTES_BY_TRACK_IMAGE);
const track = trackSectorNibbleTrackSource.read(0);
// skip to the first address epilogue
let i = findBytes(track, [0xDE, 0xAA, 0xEB]);
expect(i).toBeGreaterThan(50);
i = skipGap(track, i);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xAD]);
// data (all zeros, which is 0xAB with 5 and 3 encoding)
for (let j = 0; j < 410; j++) {
expect(track[i++]).toBe(0xAB);
}
// checksum (also zero)
expect(track[i++]).toBe(0xAB);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Field for track 0, sector 1', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource(S13_BYTES_BY_TRACK_IMAGE);
const track = trackSectorNibbleTrackSource.read(0);
// first sector prologue
let i = findBytes(track, [0xD5, 0xAA, 0xB5]);
// second sector prologue
i = findBytes(track, [0xD5, 0xAA, 0xB5], i);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// sector A = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// checksum = 0b00000101
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 0, disk sector 1 (BYTES_BY_SECTOR)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource(S13_BYTES_BY_SECTOR_IMAGE);
const track = trackSectorNibbleTrackSource.read(0);
// First data field prologue
let i = findBytes(track, [0xD5, 0xAA, 0xAD]);
// Second data field prologue
i = findBytes(track, [0xD5, 0xAA, 0xAD], i);
// Sector 1 is physical/DOS sector A.
// In 5 x 3 encoding, the lowest 3 bits of all the bytes come first,
// all mixed up in a crazy order. 0x0A is 0b00001010, so the lowest
// 3 bits are 0b010. With mixing (see Figure 3.18), this becomes:
// 0b01000, 0b01011, 0b01000
// repeated. These chunks come in repeated blocks of 0x33 (51) bytes.
//
// Because 51 * 5 is 255, there is one odd byte that is treated
// specially at the beginning.
//
// Lower 3 bits of last byte:
// 0b00010 = 0b00010 (02 -> AE)
expect(track[i++]).toBe(0xAE);
//
// Bottom 3 bits in block 1 (08 block):
// 0b01000 XOR 0b00010 = 0b01010 (0A -> BE)
// 0b01000 XOR 0b01000 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xBE);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
//
// Bottom 3 bits in block 2 (0B block):
// 0b01011 XOR 0b01000 = 0b00011 (03 -> AF)
// 0b01011 XOR 0b01011 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xAF);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
//
// Bottom 3 bits in block 1 (08 block):
// 0b01000 XOR 0b01011 = 0b00011 (03 -> AF)
// 0b01000 XOR 0b01000 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xAF);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
// Upper 5 bits of 0x0A are 0x00001:
// 0b00001 XOR 0b01000 = 0b01001 (09 -> BD)
// 0b00001 XOR 0b00001 = 0b00000 (00 -> AB) x 255
expect(track[i++]).toBe(0xBD);
for (let j = 0; j < 255; j++) {
expect(track[i++]).toBe(0xAB);
}
// checksum 0b00001 (01 -> AD)
expect(track[i++]).toBe(0xAD);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Field for track 1, sector 0', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
const track = trackSectorNibbleTrackSource.read(1);
let i = skipGap(track);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xB5]);
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// track 1 = 0b00000001
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101011);
// sector 0 = 0b00000000
expect(track[i++]).toBe(0b10101010);
expect(track[i++]).toBe(0b10101010);
// checksum = 0b00000100
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101011);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Data Field for track 1, sector 0 (BYTES_BY_TRACK)', () => {
// _Beneath Apple DOS_, DATA FIELD ENCODING, pp. 3-13 to 3-21
const trackSectorNibbleTrackSource = nibbleTrackSource(S13_BYTES_BY_TRACK_IMAGE);
const track = trackSectorNibbleTrackSource.read(1);
let i = findBytes(track, [0xDE, 0xAA, 0xEB]);
expect(i).toBeGreaterThan(50);
i = skipGap(track, i);
// prologue
i = expectSequence(track, i, [0xD5, 0xAA, 0xAD]);
// Expect data to be all 1s (track number).
// In 5 x 3 encoding, the lowest 3 bits of all the bytes come first,
// all mixed up in a crazy order. 0x01 is 0b00000001, so the lowest
// 3 bits are 0b001. With mixing (see Figure 3.18), this becomes:
// 0b00111, 0b00100, 0b00100
// repeated. These chunks come in repeated blocks of 0x33 (51) bytes.
//
// Because 51 * 5 is 255, there is one odd byte that is treated
// specially at the beginning.
//
// Lower 3 bits of last byte:
// 0b00001 = 0b00001 (01 -> AD)
expect(track[i++]).toBe(0xAD);
//
// Bottom 3 bits in block 1 (07 block):
// 0b00111 XOR 0b00001 = 0b00110 (06 -> B7)
// 0b00111 XOR 0b00111 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xB7);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
//
// Bottom 3 bits in block 2 (04 block):
// 0b00111 XOR 0b00100 = 0b00011 (03 -> AF)
// 0b00100 XOR 0b00100 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xAF);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
//
// Bottom 3 bits in block 1 (04 block):
// 0b00100 XOR 0b00100 = 0b00011 (00 -> AB)
// 0b00100 XOR 0b00100 = 0b00000 (00 -> AB) x 50
expect(track[i++]).toBe(0xAB);
for (let j = 0; j < 50; j++) {
expect(track[i++]).toBe(0xAB);
}
// Upper 5 bits of 0x01 are 0x00000:
// 0b00000 XOR 0b00100 = 0b00100 (04 -> B5)
// 0b00000 XOR 0b00000 = 0b00000 (00 -> AB) x 255
expect(track[i++]).toBe(0xB5);
for (let j = 0; j < 255; j++) {
expect(track[i++]).toBe(0xAB);
}
// checksum 0b00000 (00 -> AB)
expect(track[i++]).toBe(0xAB);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
});
it('has correct Address Fields for all tracks', () => {
// _Beneath Apple DOS_, TRACK FORMATTING, p. 3-12
const trackSectorNibbleTrackSource = nibbleTrackSource();
for (let t = 0; t < trackSectorNibbleTrackSource.numTracks(); t++) {
// We essentially seek through the track for the Address Fields
const track = trackSectorNibbleTrackSource.read(t);
let i = findBytes(track, [0xD5, 0xAA, 0xB5]);
for (let s = 0; s <= 12; s++) {
// volume 10 = 0b00001010
expect(track[i++]).toBe(0b10101111);
expect(track[i++]).toBe(0b10101010);
// convert track to 4x4 encoding
const track4x4XX = ((t & 0b10101010) >> 1) | 0b10101010;
const track4x4YY = (t & 0b01010101) | 0b10101010;
expect(track[i++]).toBe(track4x4XX);
expect(track[i++]).toBe(track4x4YY);
// convert sector to 4x4 encoding
const ss = D13O[s];
const sector4x4XX = ((ss & 0b10101010) >> 1) | 0b10101010;
const sector4x4YY = (ss & 0b01010101) | 0b10101010;
expect(track[i++]).toBe(sector4x4XX);
expect(track[i++]).toBe(sector4x4YY);
// checksum
expect(track[i++]).toBe(0b10101111 ^ track4x4XX ^ sector4x4XX);
expect(track[i++]).toBe(0b10101010 ^ track4x4YY ^ sector4x4YY);
// epilogue
i = expectSequence(track, i, [0xDE, 0xAA, 0xEB]);
// next sector
i = findBytes(track, [0xD5, 0xAA, 0xB5], i);
}
}
});
});

View File

@ -1,4 +1,5 @@
import { memory } from 'js/types';
import { concat } from 'js/util';
function generateBytesInOrder() {
const data: memory[][] = [];
@ -53,3 +54,25 @@ function generateBytesByTrack() {
}
export const BYTES_BY_TRACK: memory[][] = generateBytesByTrack();
function toImage(disk: memory[][]) {
const tracks: Uint8Array[] = [];
for (let t = 0; t < disk.length; t++) {
const track = concat(...disk[t]);
tracks.push(track);
}
return concat(...tracks);
}
export const BYTES_BY_SECTOR_IMAGE = toImage(BYTES_BY_SECTOR);
export const BYTES_BY_TRACK_IMAGE = toImage(BYTES_BY_TRACK);
function randomImage() {
const result = new Uint8Array(35 * 13 * 256);
for (let i = 0; i < result.length; i++) {
result[i] = Math.floor(Math.random() * 256);
}
return result;
}
export const RANDOM_IMAGE = randomImage();