mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Harmonize drive and disk type hierarchies
Before, the `XXXDrive` and `XXXDisk` type hierarchies were similar, but not exactly the same. For example, `encoding` and `format` were missing on some `XXXDisk` types where they existed on the `XXXDrive` type. This change attempts to bring the hierarchies closer together. However, the biggest visible consequence is the introduction of the `FLOPPY_FORMATS` array and its associated `FloppyFormat` type. This replaces `NIBBLE_FORMATS` in most places. A couple of new type guards for disk formats and disks have been added as well. All tests pass, everything compiles with no errors, and both WOZ and nibble format disks load in the emulator.
This commit is contained in:
parent
41e0609f55
commit
3a28fcb9fb
|
@ -389,6 +389,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
|
|||
(block) => new Uint8Array(block)
|
||||
),
|
||||
encoding: ENCODING_BLOCK,
|
||||
format: disk.format,
|
||||
readOnly: disk.readOnly,
|
||||
metadata: { ...disk.metadata },
|
||||
};
|
||||
|
@ -472,7 +473,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
|
|||
volume,
|
||||
readOnly
|
||||
};
|
||||
const disk = createBlockDisk(options);
|
||||
const disk = createBlockDisk(ext, options);
|
||||
|
||||
return this.setBlockVolume(drive, disk);
|
||||
}
|
||||
|
@ -485,7 +486,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
|
|||
}
|
||||
const { blocks, readOnly } = blockDisk;
|
||||
const { name } = blockDisk.metadata;
|
||||
let ext;
|
||||
let ext: '2mg' | 'po';
|
||||
let data: ArrayBuffer;
|
||||
if (this._metadata[drive]) {
|
||||
ext = '2mg';
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
DiskMetadata,
|
||||
SupportedSectors,
|
||||
FloppyDisk,
|
||||
FloppyFormat,
|
||||
} from '../formats/types';
|
||||
|
||||
import {
|
||||
|
@ -37,6 +38,7 @@ import { jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
|||
|
||||
import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2';
|
||||
import Apple2IO from '../apple2io';
|
||||
import { InfoChunk } from 'js/formats/woz';
|
||||
|
||||
/** Softswitch locations */
|
||||
const LOC = {
|
||||
|
@ -214,37 +216,42 @@ export interface Callbacks {
|
|||
/** Common information for Nibble and WOZ disks. */
|
||||
interface BaseDrive {
|
||||
/** Current disk format. */
|
||||
format: NibbleFormat;
|
||||
/** Current disk volume number. */
|
||||
volume: byte;
|
||||
format: FloppyFormat;
|
||||
/** Metadata about the disk image */
|
||||
metadata: DiskMetadata;
|
||||
/** Whether the drive write protect is on. */
|
||||
readOnly: boolean;
|
||||
/** Quarter track position of read/write head. */
|
||||
track: byte;
|
||||
/** Position of the head on the track. */
|
||||
head: byte;
|
||||
/** Current active coil in the head stepper motor. */
|
||||
phase: Phase;
|
||||
/** Whether the drive write protect is on. */
|
||||
readOnly: boolean;
|
||||
/** Whether the drive has been written to since it was loaded. */
|
||||
dirty: boolean;
|
||||
/** Metadata about the disk image */
|
||||
metadata: DiskMetadata;
|
||||
}
|
||||
|
||||
/** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */
|
||||
interface WozDrive extends BaseDrive {
|
||||
/** Woz encoding */
|
||||
encoding: typeof ENCODING_BITSTREAM;
|
||||
format: 'woz';
|
||||
/** Maps quarter tracks to data in rawTracks; `0xFF` = random garbage. */
|
||||
trackMap: byte[];
|
||||
/** Unique track bitstreams. The index is arbitrary; it is NOT the track number. */
|
||||
rawTracks: Uint8Array[];
|
||||
/** Optional `INFO` chunk from WOZ image. */
|
||||
info?: InfoChunk;
|
||||
}
|
||||
|
||||
/** Nibble format track data. */
|
||||
interface NibbleDrive extends BaseDrive {
|
||||
/** Nibble encoding */
|
||||
encoding: typeof ENCODING_NIBBLE;
|
||||
/** Format */
|
||||
format: Exclude<NibbleFormat, 'woz'>;
|
||||
/** Current disk volume number. */
|
||||
volume: byte;
|
||||
/** Nibble data. The index is the track number. */
|
||||
tracks: memory[];
|
||||
}
|
||||
|
@ -259,9 +266,9 @@ function isWozDrive(drive: Drive): drive is WozDrive {
|
|||
return drive.encoding === ENCODING_BITSTREAM;
|
||||
}
|
||||
|
||||
interface DriveState {
|
||||
format: NibbleFormat;
|
||||
encoding: typeof ENCODING_BITSTREAM | typeof ENCODING_NIBBLE;
|
||||
interface NibbleDriveState {
|
||||
format: Exclude<NibbleFormat, 'woz'>;
|
||||
encoding: typeof ENCODING_NIBBLE;
|
||||
volume: byte;
|
||||
tracks: memory[];
|
||||
track: byte;
|
||||
|
@ -274,6 +281,23 @@ interface DriveState {
|
|||
metadata: DiskMetadata;
|
||||
}
|
||||
|
||||
interface WozDriveState {
|
||||
format: 'woz';
|
||||
encoding: typeof ENCODING_BITSTREAM;
|
||||
tracks: memory[];
|
||||
track: byte;
|
||||
head: byte;
|
||||
phase: Phase;
|
||||
readOnly: boolean;
|
||||
dirty: boolean;
|
||||
trackMap: number[];
|
||||
rawTracks: Uint8Array[];
|
||||
metadata: DiskMetadata;
|
||||
info?: InfoChunk;
|
||||
}
|
||||
|
||||
type DriveState = NibbleDriveState | WozDriveState;
|
||||
|
||||
/** State of the controller for saving/restoring. */
|
||||
// TODO(flan): It's unclear whether reusing ControllerState here is a good idea.
|
||||
interface State {
|
||||
|
@ -283,33 +307,52 @@ interface State {
|
|||
}
|
||||
|
||||
function getDriveState(drive: Drive): DriveState {
|
||||
const result: DriveState = {
|
||||
format: drive.format,
|
||||
encoding: drive.encoding,
|
||||
volume: drive.volume,
|
||||
tracks: [],
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty,
|
||||
trackMap: [],
|
||||
rawTracks: [],
|
||||
metadata: { ...drive.metadata },
|
||||
};
|
||||
|
||||
if (isNibbleDrive(drive)) {
|
||||
const result: NibbleDriveState = {
|
||||
format: drive.format,
|
||||
encoding: drive.encoding,
|
||||
volume: drive.volume,
|
||||
tracks: [],
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty,
|
||||
trackMap: [],
|
||||
rawTracks: [],
|
||||
metadata: { ...drive.metadata },
|
||||
};
|
||||
for (let idx = 0; idx < drive.tracks.length; idx++) {
|
||||
result.tracks.push(new Uint8Array(drive.tracks[idx]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isWozDrive(drive)) {
|
||||
const result: WozDriveState = {
|
||||
format: drive.format,
|
||||
encoding: drive.encoding,
|
||||
tracks: [],
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
readOnly: drive.readOnly,
|
||||
dirty: drive.dirty,
|
||||
trackMap: [],
|
||||
rawTracks: [],
|
||||
metadata: { ...drive.metadata },
|
||||
};
|
||||
result.trackMap = [...drive.trackMap];
|
||||
for (let idx = 0; idx < drive.rawTracks.length; idx++) {
|
||||
result.rawTracks.push(new Uint8Array(drive.rawTracks[idx]));
|
||||
}
|
||||
if (drive.info) {
|
||||
result.info = drive.info;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
|
||||
throw new Error('Unknown drive state');
|
||||
}
|
||||
|
||||
function setDriveState(state: DriveState) {
|
||||
|
@ -318,7 +361,7 @@ function setDriveState(state: DriveState) {
|
|||
result = {
|
||||
format: state.format,
|
||||
encoding: ENCODING_NIBBLE,
|
||||
volume: state.volume,
|
||||
volume: state.volume || 254,
|
||||
tracks: [],
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
|
@ -334,7 +377,6 @@ function setDriveState(state: DriveState) {
|
|||
result = {
|
||||
format: state.format,
|
||||
encoding: ENCODING_BITSTREAM,
|
||||
volume: state.volume,
|
||||
track: state.track,
|
||||
head: state.head,
|
||||
phase: state.phase,
|
||||
|
@ -850,7 +892,6 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
const drive = this.drives[driveNo];
|
||||
return {
|
||||
format: drive.format,
|
||||
volume: drive.volume,
|
||||
track: drive.track,
|
||||
head: drive.head,
|
||||
phase: drive.phase,
|
||||
|
@ -916,7 +957,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
return true;
|
||||
}
|
||||
|
||||
setBinary(drive: DriveNumber, name: string, fmt: NibbleFormat, rawData: ArrayBuffer) {
|
||||
setBinary(drive: DriveNumber, name: string, fmt: FloppyFormat, rawData: ArrayBuffer) {
|
||||
const readOnly = false;
|
||||
const volume = 254;
|
||||
const options = {
|
||||
|
@ -983,7 +1024,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
}
|
||||
|
||||
// TODO(flan): Does not work with WOZ or D13 disks
|
||||
getBinary(drive: DriveNumber, ext?: NibbleFormat): MassStorageData | null {
|
||||
getBinary(drive: DriveNumber, ext?: Exclude<NibbleFormat, 'woz' | 'd13'>): MassStorageData | null {
|
||||
const cur = this.drives[drive];
|
||||
if (!isNibbleDrive(cur)) {
|
||||
return null;
|
||||
|
@ -995,7 +1036,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
this.sectors * tracks.length * 256;
|
||||
const data = new Uint8Array(len);
|
||||
|
||||
ext = ext ?? format;
|
||||
const extension = ext ?? format;
|
||||
let idx = 0;
|
||||
for (let t = 0; t < tracks.length; t++) {
|
||||
if (ext === 'nib') {
|
||||
|
@ -1003,7 +1044,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
idx += tracks[t].length;
|
||||
} else {
|
||||
for (let s = 0; s < 0x10; s++) {
|
||||
const sector = readSector({ ...cur, format: ext }, t, s);
|
||||
const sector = readSector({ ...cur, format: extension }, t, s);
|
||||
data.set(sector, idx);
|
||||
idx += sector.length;
|
||||
}
|
||||
|
@ -1011,7 +1052,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
|||
}
|
||||
|
||||
return {
|
||||
ext,
|
||||
ext: extension,
|
||||
metadata: { name },
|
||||
data: data.buffer,
|
||||
readOnly,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { debug, toHex } from '../util';
|
||||
import { rom as smartPortRom } from '../roms/cards/smartport';
|
||||
import { Card, Restorable, byte, word, rom } from '../types';
|
||||
import { MassStorage, BlockDisk, ENCODING_BLOCK, BlockFormat, MassStorageData } from '../formats/types';
|
||||
import { MassStorage, BlockDisk, ENCODING_BLOCK, BlockFormat, MassStorageData, DiskFormat } from '../formats/types';
|
||||
import CPU6502, { CpuState, flags } from '../cpu6502';
|
||||
import { create2MGFromBlockDisk, HeaderData, read2MGHeader } from '../formats/2mg';
|
||||
import createBlockDisk from '../formats/block';
|
||||
|
@ -129,7 +129,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
|
|||
private disks: BlockDisk[] = [];
|
||||
private busy: boolean[] = [];
|
||||
private busyTimeout: ReturnType<typeof setTimeout>[] = [];
|
||||
private ext: string[] = [];
|
||||
private ext: DiskFormat[] = [];
|
||||
private metadata: Array<HeaderData | null> = [];
|
||||
|
||||
constructor(
|
||||
|
@ -522,6 +522,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
|
|||
(block) => new Uint8Array(block)
|
||||
),
|
||||
encoding: ENCODING_BLOCK,
|
||||
format: disk.format,
|
||||
readOnly: disk.readOnly,
|
||||
metadata: { ...disk.metadata },
|
||||
};
|
||||
|
@ -539,6 +540,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
|
|||
(block) => new Uint8Array(block)
|
||||
),
|
||||
encoding: ENCODING_BLOCK,
|
||||
format: disk.format,
|
||||
readOnly: disk.readOnly,
|
||||
metadata: { ...disk.metadata },
|
||||
};
|
||||
|
@ -547,7 +549,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
|
|||
);
|
||||
}
|
||||
|
||||
setBinary(drive: DriveNumber, name: string, fmt: string, rawData: ArrayBuffer) {
|
||||
setBinary(drive: DriveNumber, name: string, fmt: BlockFormat, rawData: ArrayBuffer) {
|
||||
let volume = 254;
|
||||
let readOnly = false;
|
||||
if (fmt === '2mg') {
|
||||
|
@ -568,7 +570,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
|
|||
};
|
||||
|
||||
this.ext[drive] = fmt;
|
||||
this.disks[drive] = createBlockDisk(options);
|
||||
this.disks[drive] = createBlockDisk(fmt, options);
|
||||
this.callbacks?.label(drive, name);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BLOCK_FORMATS, DISK_FORMATS, DriveNumber, MassStorage, NIBBLE_FORMATS } from 'js/formats/types';
|
||||
import { BLOCK_FORMATS, DISK_FORMATS, DriveNumber, FLOPPY_FORMATS, MassStorage } from 'js/formats/types';
|
||||
import { h, JSX, RefObject } from 'preact';
|
||||
import { useEffect, useRef } from 'preact/hooks';
|
||||
import { loadLocalFile } from './util/files';
|
||||
|
@ -7,7 +7,7 @@ import { spawn } from './util/promises';
|
|||
export interface DiskDragTargetProps<T> extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
storage: MassStorage<T> | undefined;
|
||||
drive?: DriveNumber;
|
||||
formats: typeof NIBBLE_FORMATS
|
||||
formats: typeof FLOPPY_FORMATS
|
||||
| typeof BLOCK_FORMATS
|
||||
| typeof DISK_FORMATS;
|
||||
dropRef?: RefObject<HTMLElement>;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { FileModal } from './FileModal';
|
|||
|
||||
import styles from './css/DiskII.module.css';
|
||||
import { DiskDragTarget } from './DiskDragTarget';
|
||||
import { NIBBLE_FORMATS } from 'js/formats/types';
|
||||
import { FLOPPY_FORMATS } from 'js/formats/types';
|
||||
import { DownloadModal } from './DownloadModal';
|
||||
|
||||
/**
|
||||
|
@ -66,7 +66,7 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
|||
className={styles.disk}
|
||||
storage={disk2}
|
||||
drive={number}
|
||||
formats={NIBBLE_FORMATS}
|
||||
formats={FLOPPY_FORMATS}
|
||||
onError={setError}
|
||||
>
|
||||
<FileModal disk2={disk2} number={number} onClose={doClose} isOpen={modalOpen} />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { h, Fragment, JSX } from 'preact';
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks';
|
||||
import { DiskDescriptor, DriveNumber, NibbleFormat, NIBBLE_FORMATS } from '../formats/types';
|
||||
import { DiskDescriptor, DriveNumber, FLOPPY_FORMATS, NibbleFormat } from '../formats/types';
|
||||
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||
import { loadLocalNibbleFile, loadJSON, getHashParts, setHashParts } from './util/files';
|
||||
import DiskII from '../cards/disk2';
|
||||
|
@ -15,7 +15,7 @@ import styles from './css/FileModal.module.css';
|
|||
const DISK_TYPES: FilePickerAcceptType[] = [
|
||||
{
|
||||
description: 'Disk Images',
|
||||
accept: { 'application/octet-stream': NIBBLE_FORMATS.map(x => '.' + x) },
|
||||
accept: { 'application/octet-stream': FLOPPY_FORMATS.map(x => '.' + x) },
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { h, Fragment } from 'preact';
|
|||
import { useMemo } from 'preact/hooks';
|
||||
import cs from 'classnames';
|
||||
import { Apple2 as Apple2Impl } from 'js/apple2';
|
||||
import { BlockDisk, DiskFormat, DriveNumber, MassStorage, NibbleDisk } from 'js/formats/types';
|
||||
import { BlockDisk, DiskFormat, DriveNumber, FloppyDisk, isBlockDiskFormat, isNibbleDisk, MassStorage } from 'js/formats/types';
|
||||
import { slot } from 'js/apple2io';
|
||||
import DiskII from 'js/cards/disk2';
|
||||
import SmartPort from 'js/cards/smartport';
|
||||
|
@ -38,7 +38,7 @@ const formatDate = (date: Date) => {
|
|||
* @param disk NibbleDisk or BlockDisk
|
||||
* @returns true if is BlockDisk
|
||||
*/
|
||||
function isBlockDisk(disk: NibbleDisk | BlockDisk): disk is BlockDisk {
|
||||
function isBlockDisk(disk: FloppyDisk | BlockDisk): disk is BlockDisk {
|
||||
return !!((disk as BlockDisk).blocks);
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ const DiskInfo = ({ massStorage, drive, setFileData }: DiskInfoProps) => {
|
|||
if (massStorageData) {
|
||||
const { data, readOnly, ext } = massStorageData;
|
||||
const { name } = massStorageData.metadata;
|
||||
let disk: BlockDisk | NibbleDisk | null = null;
|
||||
let disk: BlockDisk | FloppyDisk | null = null;
|
||||
if (ext === '2mg') {
|
||||
disk = createDiskFrom2MG({
|
||||
name,
|
||||
|
@ -277,8 +277,8 @@ const DiskInfo = ({ massStorage, drive, setFileData }: DiskInfoProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!disk) {
|
||||
disk = createBlockDisk({
|
||||
if (!disk && isBlockDiskFormat(ext)) {
|
||||
disk = createBlockDisk(ext, {
|
||||
name,
|
||||
rawData: data,
|
||||
readOnly,
|
||||
|
@ -330,7 +330,7 @@ const DiskInfo = ({ massStorage, drive, setFileData }: DiskInfoProps) => {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
} else if (isNibbleDisk(disk)) {
|
||||
const dos = new DOS33(disk);
|
||||
return (
|
||||
<div className={styles.volume}>
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { includes, word } from 'js/types';
|
||||
import { initGamepad } from 'js/ui/gamepad';
|
||||
import Disk2 from 'js/cards/disk2';
|
||||
import SmartPort from 'js/cards/smartport';
|
||||
import Debugger from 'js/debugger';
|
||||
import {
|
||||
BlockFormat,
|
||||
BLOCK_FORMATS,
|
||||
DISK_FORMATS,
|
||||
DriveNumber,
|
||||
FloppyFormat,
|
||||
FLOPPY_FORMATS,
|
||||
JSONDisk,
|
||||
MassStorage,
|
||||
NibbleFormat,
|
||||
NIBBLE_FORMATS,
|
||||
} from 'js/formats/types';
|
||||
import Disk2 from 'js/cards/disk2';
|
||||
import SmartPort from 'js/cards/smartport';
|
||||
import Debugger from 'js/debugger';
|
||||
import { includes, word } from 'js/types';
|
||||
import { initGamepad } from 'js/ui/gamepad';
|
||||
|
||||
type ProgressCallback = (current: number, total: number) => void;
|
||||
|
||||
|
@ -46,8 +46,8 @@ export const getNameAndExtension = (url: string) => {
|
|||
};
|
||||
|
||||
export const loadLocalFile = (
|
||||
storage: MassStorage<NibbleFormat|BlockFormat>,
|
||||
formats: typeof NIBBLE_FORMATS | typeof BLOCK_FORMATS | typeof DISK_FORMATS,
|
||||
storage: MassStorage<FloppyFormat|BlockFormat>,
|
||||
formats: typeof FLOPPY_FORMATS | typeof BLOCK_FORMATS | typeof DISK_FORMATS,
|
||||
number: DriveNumber,
|
||||
file: File,
|
||||
) => {
|
||||
|
@ -94,7 +94,7 @@ export const loadLocalBlockFile = (smartPort: SmartPort, number: DriveNumber, fi
|
|||
* @returns true if successful
|
||||
*/
|
||||
export const loadLocalNibbleFile = (disk2: Disk2, number: DriveNumber, file: File) => {
|
||||
return loadLocalFile(disk2, NIBBLE_FORMATS, number, file);
|
||||
return loadLocalFile(disk2, FLOPPY_FORMATS, number, file);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -117,7 +117,7 @@ export const loadJSON = async (
|
|||
throw new Error(`Error loading: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json() as JSONDisk;
|
||||
if (!includes(NIBBLE_FORMATS, data.type)) {
|
||||
if (!includes(FLOPPY_FORMATS, data.type)) {
|
||||
throw new Error(`Type "${data.type}" not recognized.`);
|
||||
}
|
||||
disk2.setDisk(number, data);
|
||||
|
@ -209,7 +209,7 @@ export const loadHttpNibbleFile = async (
|
|||
return loadJSON(disk2, number, url);
|
||||
}
|
||||
const { name, ext } = getNameAndExtension(url);
|
||||
if (!includes(NIBBLE_FORMATS, ext)) {
|
||||
if (!includes(FLOPPY_FORMATS, ext)) {
|
||||
throw new Error(`Extension "${ext}" not recognized.`);
|
||||
}
|
||||
const data = await loadHttpFile(url, signal, onProgress);
|
||||
|
@ -241,7 +241,7 @@ export class SmartStorageBroker implements MassStorage<unknown> {
|
|||
} else {
|
||||
throw new Error(`Unable to load "${name}"`);
|
||||
}
|
||||
} else if (includes(NIBBLE_FORMATS, ext)) {
|
||||
} else if (includes(FLOPPY_FORMATS, ext)) {
|
||||
this.disk2.setBinary(drive, name, ext, data);
|
||||
} else {
|
||||
throw new Error(`Unable to load "${name}"`);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { DiskOptions, BlockDisk, ENCODING_BLOCK } from './types';
|
||||
import { DiskOptions, BlockDisk, ENCODING_BLOCK, BlockFormat } from './types';
|
||||
|
||||
/**
|
||||
* Returns a `Disk` object for a block volume with block-ordered data.
|
||||
* @param options the disk image and options
|
||||
*/
|
||||
export default function createBlockDisk(options: DiskOptions): BlockDisk {
|
||||
export default function createBlockDisk(fmt: BlockFormat, options: DiskOptions): BlockDisk {
|
||||
const { rawData, readOnly, name } = options;
|
||||
|
||||
if (!rawData) {
|
||||
|
@ -20,6 +20,7 @@ export default function createBlockDisk(options: DiskOptions): BlockDisk {
|
|||
|
||||
const disk: BlockDisk = {
|
||||
encoding: ENCODING_BLOCK,
|
||||
format: fmt,
|
||||
blocks,
|
||||
metadata: { name },
|
||||
readOnly,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { includes, memory } from '../types';
|
||||
import { base64_decode } from '../base64';
|
||||
import { DiskOptions, FloppyDisk, JSONDisk, NibbleFormat, NIBBLE_FORMATS } from './types';
|
||||
import { BitstreamFormat, DiskOptions, FloppyDisk, FloppyFormat, JSONDisk, NibbleDisk, NibbleFormat, NIBBLE_FORMATS, WozDisk } from './types';
|
||||
import createDiskFrom2MG from './2mg';
|
||||
import createDiskFromD13 from './d13';
|
||||
import createDiskFromDOS from './do';
|
||||
|
@ -8,13 +8,13 @@ import createDiskFromProDOS from './po';
|
|||
import createDiskFromWoz from './woz';
|
||||
import createDiskFromNibble from './nib';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fmt Type of
|
||||
* @param options
|
||||
* @returns A nibblized disk
|
||||
*/
|
||||
export function createDisk(fmt: NibbleFormat, options: DiskOptions): FloppyDisk | null {
|
||||
/** Creates a `NibbleDisk` from the given format and options. */
|
||||
export function createDisk(fmt: NibbleFormat, options: DiskOptions): NibbleDisk | null;
|
||||
/** Creates a `WozDisk` from the given format and options. */
|
||||
export function createDisk(fmt: BitstreamFormat, options: DiskOptions): WozDisk | null;
|
||||
/** Creates a `FloppyDisk` (either a `NibbleDisk` or a `WozDisk`) from the given format and options. */
|
||||
export function createDisk(fmt: FloppyFormat, options: DiskOptions): FloppyDisk | null;
|
||||
export function createDisk(fmt: FloppyFormat, options: DiskOptions): FloppyDisk | null {
|
||||
let disk: FloppyDisk | null = null;
|
||||
|
||||
switch (fmt) {
|
||||
|
@ -42,7 +42,8 @@ export function createDisk(fmt: NibbleFormat, options: DiskOptions): FloppyDisk
|
|||
return disk;
|
||||
}
|
||||
|
||||
export function createDiskFromJsonDisk(disk: JSONDisk): FloppyDisk | null {
|
||||
/** Creates a NibbleDisk from JSON */
|
||||
export function createDiskFromJsonDisk(disk: JSONDisk): NibbleDisk | null {
|
||||
const fmt = disk.type;
|
||||
const readOnly = disk.readOnly;
|
||||
const name = disk.name;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { bit, byte, memory } from '../types';
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { bytify, debug, toHex } from '../util';
|
||||
import { NibbleDisk, ENCODING_NIBBLE, JSONDisk } from './types';
|
||||
import { NibbleDisk, ENCODING_NIBBLE, JSONDisk, isNibbleDiskFormat } from './types';
|
||||
|
||||
/**
|
||||
* DOS 3.3 Physical sector order (index is physical sector, value is DOS sector).
|
||||
|
@ -550,6 +550,9 @@ export function jsonDecode(data: string): NibbleDisk {
|
|||
}
|
||||
tracks[t] = bytify(track);
|
||||
}
|
||||
if (!isNibbleDiskFormat(json.type)) {
|
||||
throw new Error(`JSON disks of type ${json.type} are not supported`);
|
||||
}
|
||||
const disk: NibbleDisk = {
|
||||
volume: v,
|
||||
format: json.type,
|
||||
|
|
|
@ -69,17 +69,19 @@ export const ENCODING_BITSTREAM = 'bitstream';
|
|||
export const ENCODING_BLOCK = 'block';
|
||||
|
||||
export interface FloppyDisk extends Disk {
|
||||
encoding: typeof ENCODING_NIBBLE | typeof ENCODING_BITSTREAM;
|
||||
tracks: memory[];
|
||||
}
|
||||
|
||||
export interface NibbleDisk extends FloppyDisk {
|
||||
encoding: typeof ENCODING_NIBBLE;
|
||||
format: DiskFormat;
|
||||
format: Exclude<NibbleFormat, 'woz'>;
|
||||
volume: byte;
|
||||
}
|
||||
|
||||
export interface WozDisk extends FloppyDisk {
|
||||
encoding: typeof ENCODING_BITSTREAM;
|
||||
format: 'woz';
|
||||
trackMap: number[];
|
||||
rawTracks: Uint8Array[];
|
||||
info: InfoChunk | undefined;
|
||||
|
@ -87,14 +89,13 @@ export interface WozDisk extends FloppyDisk {
|
|||
|
||||
export interface BlockDisk extends Disk {
|
||||
encoding: typeof ENCODING_BLOCK;
|
||||
format: BlockFormat;
|
||||
blocks: Uint8Array[];
|
||||
}
|
||||
|
||||
/**
|
||||
* File types supported by the disk format processors and
|
||||
* block devices.
|
||||
* File types supported by floppy devices in nibble mode.
|
||||
*/
|
||||
|
||||
export const NIBBLE_FORMATS = [
|
||||
'2mg',
|
||||
'd13',
|
||||
|
@ -102,21 +103,61 @@ export const NIBBLE_FORMATS = [
|
|||
'dsk',
|
||||
'po',
|
||||
'nib',
|
||||
'woz'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* File types supported by floppy devices in bitstream mode.
|
||||
*/
|
||||
export const BITSTREAM_FORMATS = [
|
||||
'woz',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* All file types supported by floppy devices.
|
||||
*/
|
||||
export const FLOPPY_FORMATS = [
|
||||
...NIBBLE_FORMATS,
|
||||
...BITSTREAM_FORMATS,
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* File types supported by block devices.
|
||||
*/
|
||||
export const BLOCK_FORMATS = [
|
||||
'2mg',
|
||||
'hdv',
|
||||
'po',
|
||||
] as const;
|
||||
|
||||
export const DISK_FORMATS = [...NIBBLE_FORMATS, ...BLOCK_FORMATS] as const;
|
||||
/**
|
||||
* All supported disk formats.
|
||||
*/
|
||||
export const DISK_FORMATS = [
|
||||
...FLOPPY_FORMATS,
|
||||
...BLOCK_FORMATS,
|
||||
] as const;
|
||||
|
||||
export type FloppyFormat = MemberOf<typeof FLOPPY_FORMATS>;
|
||||
export type NibbleFormat = MemberOf<typeof NIBBLE_FORMATS>;
|
||||
export type BitstreamFormat = 'woz';
|
||||
export type BlockFormat = MemberOf<typeof BLOCK_FORMATS>;
|
||||
export type DiskFormat = MemberOf<typeof DISK_FORMATS>;
|
||||
|
||||
/** Type guard for nibble disk formats. */
|
||||
export function isNibbleDiskFormat(f: DiskFormat): f is NibbleFormat {
|
||||
return f in NIBBLE_FORMATS;
|
||||
}
|
||||
|
||||
/** Type guard for block disk formats. */
|
||||
export function isBlockDiskFormat(f: DiskFormat): f is BlockFormat {
|
||||
return f in BLOCK_FORMATS;
|
||||
}
|
||||
|
||||
/** Type guard for NibbleDisks */
|
||||
export function isNibbleDisk(disk: Disk): disk is NibbleDisk {
|
||||
return (disk as NibbleDisk)?.encoding === ENCODING_NIBBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base format for JSON defined disks
|
||||
*/
|
||||
|
@ -180,7 +221,7 @@ export interface ProcessBinaryMessage {
|
|||
type: typeof PROCESS_BINARY;
|
||||
payload: {
|
||||
drive: DriveNumber;
|
||||
fmt: NibbleFormat;
|
||||
fmt: FloppyFormat;
|
||||
options: DiskOptions;
|
||||
};
|
||||
}
|
||||
|
@ -227,7 +268,7 @@ export type FormatWorkerResponse =
|
|||
|
||||
export interface MassStorageData {
|
||||
metadata: DiskMetadata;
|
||||
ext: string;
|
||||
ext: DiskFormat;
|
||||
readOnly: boolean;
|
||||
volume?: byte;
|
||||
data: ArrayBuffer;
|
||||
|
|
|
@ -293,6 +293,7 @@ export default function createDiskFromWoz(options: DiskOptions): WozDisk {
|
|||
|
||||
const disk: WozDisk = {
|
||||
encoding: ENCODING_BITSTREAM,
|
||||
format: 'woz',
|
||||
trackMap: tmap?.trackMap || [],
|
||||
tracks: trks?.tracks || [],
|
||||
rawTracks: trks?.rawTracks || [],
|
||||
|
|
|
@ -11,10 +11,10 @@ import {
|
|||
DriveNumber,
|
||||
DRIVE_NUMBERS,
|
||||
MassStorage,
|
||||
NIBBLE_FORMATS,
|
||||
JSONBinaryImage,
|
||||
JSONDisk,
|
||||
BlockFormat
|
||||
BlockFormat,
|
||||
FLOPPY_FORMATS
|
||||
} from '../formats/types';
|
||||
import { initGamepad } from './gamepad';
|
||||
import KeyBoard from './keyboard';
|
||||
|
@ -392,7 +392,7 @@ function doLoadLocalDisk(drive: DriveNumber, file: File) {
|
|||
}
|
||||
} else {
|
||||
if (
|
||||
includes(NIBBLE_FORMATS, ext) &&
|
||||
includes(FLOPPY_FORMATS, ext) &&
|
||||
_disk2.setBinary(drive, name, ext, result)
|
||||
) {
|
||||
initGamepad();
|
||||
|
@ -459,7 +459,7 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
|
|||
}
|
||||
} else {
|
||||
if (
|
||||
includes(NIBBLE_FORMATS, ext) &&
|
||||
includes(FLOPPY_FORMATS, ext) &&
|
||||
_disk2.setBinary(drive, name, ext, data)
|
||||
) {
|
||||
initGamepad();
|
||||
|
|
|
@ -193,6 +193,7 @@ describe('2mg format', () => {
|
|||
metadata: { name: 'Good disk' },
|
||||
readOnly: false,
|
||||
encoding: ENCODING_BLOCK,
|
||||
format: 'hdv',
|
||||
};
|
||||
const image = create2MGFromBlockDisk(header, disk);
|
||||
expect(VALID_PRODOS_IMAGE.buffer).toEqual(image);
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('woz', () => {
|
|||
metadata: { name: 'Mock Woz 1' },
|
||||
readOnly: true,
|
||||
encoding: ENCODING_BITSTREAM,
|
||||
format: 'woz',
|
||||
trackMap: mockTMAP,
|
||||
rawTracks: [new Uint8Array([
|
||||
1, 1, 0, 1, 0, 1, 0, 1,
|
||||
|
@ -64,6 +65,7 @@ describe('woz', () => {
|
|||
},
|
||||
readOnly: true,
|
||||
encoding: ENCODING_BITSTREAM,
|
||||
format: 'woz',
|
||||
trackMap: mockTMAP,
|
||||
rawTracks: [new Uint8Array([
|
||||
1, 1, 0, 1, 0, 1, 0, 1,
|
||||
|
|
Loading…
Reference in New Issue
Block a user