mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Woz to TypeScript (#84)
Woz to TypeScript, with tests added before conversion.
This commit is contained in:
parent
77b13532da
commit
044e28e050
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
import { base64_encode} from '../base64';
|
import { base64_encode} from '../base64';
|
||||||
import type {
|
import type {
|
||||||
bit,
|
|
||||||
byte,
|
byte,
|
||||||
Card,
|
Card,
|
||||||
memory,
|
memory,
|
||||||
|
@ -198,7 +197,7 @@ interface WozDrive extends BaseDrive {
|
||||||
/** Maps quarter tracks to data in rawTracks; `0xFF` = random garbage. */
|
/** Maps quarter tracks to data in rawTracks; `0xFF` = random garbage. */
|
||||||
trackMap: byte[];
|
trackMap: byte[];
|
||||||
/** Unique track bitstreams. The index is arbitrary; it is NOT the track number. */
|
/** Unique track bitstreams. The index is arbitrary; it is NOT the track number. */
|
||||||
rawTracks: bit[][];
|
rawTracks: Uint8Array[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Nibble format track data. */
|
/** Nibble format track data. */
|
||||||
|
@ -231,7 +230,7 @@ interface DriveState {
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
dirty: boolean,
|
dirty: boolean,
|
||||||
trackMap: number[],
|
trackMap: number[],
|
||||||
rawTracks: bit[][],
|
rawTracks: Uint8Array[],
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -267,7 +266,7 @@ function getDriveState(drive: Drive): DriveState {
|
||||||
if (isWozDrive(drive)) {
|
if (isWozDrive(drive)) {
|
||||||
result.trackMap = [...drive.trackMap];
|
result.trackMap = [...drive.trackMap];
|
||||||
for (let idx = 0; idx < drive.rawTracks.length; idx++) {
|
for (let idx = 0; idx < drive.rawTracks.length; idx++) {
|
||||||
result.rawTracks.push([...drive.rawTracks[idx]]);
|
result.rawTracks.push(new Uint8Array(drive.rawTracks[idx]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -306,7 +305,7 @@ function setDriveState(state: DriveState) {
|
||||||
rawTracks: [],
|
rawTracks: [],
|
||||||
};
|
};
|
||||||
for (let idx = 0; idx < state.rawTracks.length; idx++) {
|
for (let idx = 0; idx < state.rawTracks.length; idx++) {
|
||||||
result.rawTracks.push([...state.rawTracks[idx]]);
|
result.rawTracks.push(new Uint8Array(state.rawTracks[idx]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -415,11 +414,11 @@ export default class DiskII implements Card {
|
||||||
if (!isWozDrive(this.cur)) {
|
if (!isWozDrive(this.cur)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const track: bit[] =
|
const track =
|
||||||
this.cur.rawTracks[this.cur.trackMap[this.cur.track]] || [0];
|
this.cur.rawTracks[this.cur.trackMap[this.cur.track]] || [0];
|
||||||
|
|
||||||
while (workCycles-- > 0) {
|
while (workCycles-- > 0) {
|
||||||
let pulse: bit = 0;
|
let pulse: number = 0;
|
||||||
if (this.clock == 4) {
|
if (this.clock == 4) {
|
||||||
pulse = track[this.cur.head];
|
pulse = track[this.cur.head];
|
||||||
if (!pulse) {
|
if (!pulse) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { includes, memory } from '../types';
|
import { includes, memory } from '../types';
|
||||||
import { base64_decode } from '../base64';
|
import { base64_decode } from '../base64';
|
||||||
import { Disk, NibbleFormat, DiskOptions, JSONDisk, NIBBLE_FORMATS, NibbleDisk } from './types';
|
import { DiskOptions, FloppyDisk, JSONDisk, NibbleFormat, NIBBLE_FORMATS } from './types';
|
||||||
import createDiskFrom2MG from './2mg';
|
import createDiskFrom2MG from './2mg';
|
||||||
import createDiskFromD13 from './d13';
|
import createDiskFromD13 from './d13';
|
||||||
import createDiskFromDOS from './do';
|
import createDiskFromDOS from './do';
|
||||||
|
@ -14,8 +14,8 @@ import createDiskFromNibble from './nib';
|
||||||
* @param options
|
* @param options
|
||||||
* @returns A nibblized disk
|
* @returns A nibblized disk
|
||||||
*/
|
*/
|
||||||
export function createDisk(fmt: NibbleFormat, options: DiskOptions): NibbleDisk | null {
|
export function createDisk(fmt: NibbleFormat, options: DiskOptions): FloppyDisk | null {
|
||||||
let disk: NibbleDisk | null = null;
|
let disk: FloppyDisk | null = null;
|
||||||
|
|
||||||
switch (fmt) {
|
switch (fmt) {
|
||||||
case '2mg':
|
case '2mg':
|
||||||
|
@ -42,7 +42,7 @@ export function createDisk(fmt: NibbleFormat, options: DiskOptions): NibbleDisk
|
||||||
return disk;
|
return disk;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDiskFromJsonDisk(disk: JSONDisk): Disk | null {
|
export function createDiskFromJsonDisk(disk: JSONDisk): FloppyDisk | null {
|
||||||
const fmt = disk.type;
|
const fmt = disk.type;
|
||||||
const readOnly = disk.readOnly;
|
const readOnly = disk.readOnly;
|
||||||
const name = disk.name;
|
const name = disk.name;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { byte, memory } from '../types';
|
import { bit, byte, memory } from '../types';
|
||||||
import { base64_decode, base64_encode } from '../base64';
|
import { base64_decode, base64_encode } from '../base64';
|
||||||
import { bytify, debug, toHex } from '../util';
|
import { bytify, debug, toHex } from '../util';
|
||||||
import { NibbleDisk, ENCODING_NIBBLE } from './types';
|
import { NibbleDisk, ENCODING_NIBBLE } from './types';
|
||||||
|
@ -501,6 +501,12 @@ export function jsonDecode(data: string): NibbleDisk {
|
||||||
return disk;
|
return disk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debugging method that displays the logical sector ordering of a nibblized disk
|
||||||
|
*
|
||||||
|
* @param disk
|
||||||
|
*/
|
||||||
|
|
||||||
export function analyseDisk(disk: NibbleDisk) {
|
export function analyseDisk(disk: NibbleDisk) {
|
||||||
for (let track = 0; track < 35; track++) {
|
for (let track = 0; track < 35; track++) {
|
||||||
let outStr = `${toHex(track)}: `;
|
let outStr = `${toHex(track)}: `;
|
||||||
|
@ -555,5 +561,38 @@ export function analyseDisk(disk: NibbleDisk) {
|
||||||
}
|
}
|
||||||
debug(outStr);
|
debug(outStr);
|
||||||
}
|
}
|
||||||
return new Uint8Array();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param bits Bitstream containing nibbles
|
||||||
|
* @param offset Offset into bitstream to start nibblizing
|
||||||
|
* @returns The next nibble in the bitstream
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function grabNibble(bits: bit[], offset: number) {
|
||||||
|
let nibble = 0;
|
||||||
|
let waitForOne = true;
|
||||||
|
|
||||||
|
while (offset < bits.length) {
|
||||||
|
const bit = bits[offset];
|
||||||
|
if (bit) {
|
||||||
|
nibble = (nibble << 1) | 0x01;
|
||||||
|
waitForOne = false;
|
||||||
|
} else {
|
||||||
|
if (!waitForOne) {
|
||||||
|
nibble = nibble << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nibble & 0x80) {
|
||||||
|
// nibble complete return it
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nibble: nibble,
|
||||||
|
offset: offset
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { bit, byte, memory, MemberOf } from '../types';
|
import type { byte, memory, MemberOf } from '../types';
|
||||||
import type { GamepadConfiguration } from '../ui/types';
|
import type { GamepadConfiguration } from '../ui/types';
|
||||||
|
|
||||||
export const DRIVE_NUMBERS = [1, 2] as const;
|
export const DRIVE_NUMBERS = [1, 2] as const;
|
||||||
|
@ -42,18 +42,20 @@ export const ENCODING_NIBBLE = 'nibble';
|
||||||
export const ENCODING_BITSTREAM = 'bitstream';
|
export const ENCODING_BITSTREAM = 'bitstream';
|
||||||
export const ENCODING_BLOCK = 'block';
|
export const ENCODING_BLOCK = 'block';
|
||||||
|
|
||||||
export interface NibbleDisk extends Disk {
|
export interface FloppyDisk extends Disk {
|
||||||
encoding: typeof ENCODING_NIBBLE
|
|
||||||
format: DiskFormat
|
|
||||||
volume: byte
|
|
||||||
tracks: memory[]
|
tracks: memory[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WozDisk extends Disk {
|
export interface NibbleDisk extends FloppyDisk {
|
||||||
|
encoding: typeof ENCODING_NIBBLE
|
||||||
|
format: DiskFormat
|
||||||
|
volume: byte
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WozDisk extends FloppyDisk {
|
||||||
encoding: typeof ENCODING_BITSTREAM
|
encoding: typeof ENCODING_BITSTREAM
|
||||||
trackMap: number[]
|
trackMap: number[]
|
||||||
rawTracks: bit[][]
|
rawTracks: Uint8Array[]
|
||||||
tracks: memory[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockDisk extends Disk {
|
export interface BlockDisk extends Disk {
|
||||||
|
|
|
@ -1,282 +0,0 @@
|
||||||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
||||||
* documentation for any purpose is hereby granted without fee, provided that
|
|
||||||
* the above copyright notice appear in all copies and that both that
|
|
||||||
* copyright notice and this permission notice appear in supporting
|
|
||||||
* documentation. No representations are made about the suitability of this
|
|
||||||
* software for any purpose. It is provided "as is" without express or
|
|
||||||
* implied warranty.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { debug, toHex } from '../util';
|
|
||||||
import { ENCODING_BITSTREAM } from './types';
|
|
||||||
|
|
||||||
const WOZ_HEADER_START = 0;
|
|
||||||
const WOZ_HEADER_SIZE = 12;
|
|
||||||
|
|
||||||
const WOZ1_SIGNATURE = 0x315A4F57;
|
|
||||||
const WOZ2_SIGNATURE = 0x325A4F57;
|
|
||||||
const WOZ_INTEGRITY_CHECK = 0x0a0d0aff;
|
|
||||||
|
|
||||||
function stringFromBytes(data, start, end) {
|
|
||||||
return String.fromCharCode.apply(
|
|
||||||
null,
|
|
||||||
new Uint8Array(data.buffer.slice(data.byteOffset + start, data.byteOffset + end))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function grabNibble(bits, offset) {
|
|
||||||
let nibble = 0;
|
|
||||||
let waitForOne = true;
|
|
||||||
|
|
||||||
while (offset < bits.length) {
|
|
||||||
const bit = bits[offset];
|
|
||||||
if (bit) {
|
|
||||||
nibble = (nibble << 1) | 0x01;
|
|
||||||
waitForOne = false;
|
|
||||||
} else {
|
|
||||||
if (!waitForOne) {
|
|
||||||
nibble = nibble << 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nibble & 0x80) {
|
|
||||||
// nibble complete return it
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nibble: nibble,
|
|
||||||
offset: offset
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function InfoChunk(data) {
|
|
||||||
Object.assign(this, {
|
|
||||||
version: data.getUint8(0),
|
|
||||||
diskType: data.getUint8(1),
|
|
||||||
writeProtected: data.getUint8(2),
|
|
||||||
synchronized: data.getUint8(3),
|
|
||||||
cleaned: data.getUint8(4),
|
|
||||||
creator: stringFromBytes(data, 5, 37)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.version === 2) {
|
|
||||||
Object.assign(this, {
|
|
||||||
sides: data.getUint8(37),
|
|
||||||
bootSector: data.getUint8(38),
|
|
||||||
bitTiming: data.getUint8(39),
|
|
||||||
compatibleHardware: data.getUint16(40, true),
|
|
||||||
requiredRAM: data.getUint16(42, true),
|
|
||||||
largestTrack: data.getUint16(44, true)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TMapChunk(data) {
|
|
||||||
this.trackMap = [];
|
|
||||||
|
|
||||||
for (let idx = 0; idx < 160; idx++) {
|
|
||||||
this.trackMap.push(data.getUint8(idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TrksChunk(data) {
|
|
||||||
const WOZ_TRACK_SIZE = 6656;
|
|
||||||
const WOZ_TRACK_INFO_BITS = 6648;
|
|
||||||
|
|
||||||
this.rawTracks = [];
|
|
||||||
this.tracks = [];
|
|
||||||
for (let trackNo = 0, idx = 0; idx < data.byteLength; idx += WOZ_TRACK_SIZE, trackNo++) {
|
|
||||||
let track = [];
|
|
||||||
const rawTrack = [];
|
|
||||||
const slice = data.buffer.slice(data.byteOffset + idx, data.byteOffset + idx + WOZ_TRACK_SIZE);
|
|
||||||
const trackData = new Uint8Array(slice);
|
|
||||||
const trackInfo = new DataView(slice);
|
|
||||||
const trackBitCount = trackInfo.getUint16(WOZ_TRACK_INFO_BITS, true);
|
|
||||||
for (let jdx = 0; jdx < trackBitCount; jdx++) {
|
|
||||||
const byteIndex = jdx >> 3;
|
|
||||||
const bitIndex = 7 - (jdx & 0x07);
|
|
||||||
rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
track = [];
|
|
||||||
let offset = 0;
|
|
||||||
while (offset < rawTrack.length) {
|
|
||||||
const result = grabNibble(rawTrack, offset);
|
|
||||||
if (!result.nibble) { break; }
|
|
||||||
track.push(result.nibble);
|
|
||||||
offset = result.offset + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tracks[trackNo] = track;
|
|
||||||
this.rawTracks[trackNo] = rawTrack;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TrksChunk2(data) {
|
|
||||||
let trackNo;
|
|
||||||
this.trks = [];
|
|
||||||
for (trackNo = 0; trackNo < 160; trackNo++) {
|
|
||||||
const startBlock = data.getUint16(trackNo * 8, true);
|
|
||||||
const blockCount = data.getUint16(trackNo * 8 + 2, true);
|
|
||||||
const bitCount = data.getUint32(trackNo * 8 + 4, true);
|
|
||||||
if (bitCount === 0) { break; }
|
|
||||||
this.trks.push({
|
|
||||||
startBlock: startBlock,
|
|
||||||
blockCount: blockCount,
|
|
||||||
bitCount: bitCount
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.tracks = [];
|
|
||||||
this.rawTracks = [];
|
|
||||||
|
|
||||||
const bits = data.buffer;
|
|
||||||
for (trackNo = 0; trackNo < this.trks.length; trackNo++) {
|
|
||||||
const trk = this.trks[trackNo];
|
|
||||||
let track = [];
|
|
||||||
const rawTrack = [];
|
|
||||||
const start = trk.startBlock * 512;
|
|
||||||
const end = start + trk.blockCount * 512;
|
|
||||||
const slice = bits.slice(start, end);
|
|
||||||
const trackData = new Uint8Array(slice);
|
|
||||||
for (let jdx = 0; jdx < trk.bitCount; jdx++) {
|
|
||||||
const byteIndex = jdx >> 3;
|
|
||||||
const bitIndex = 7 - (jdx & 0x07);
|
|
||||||
rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
track = [];
|
|
||||||
let offset = 0;
|
|
||||||
while (offset < rawTrack.length) {
|
|
||||||
const result = grabNibble(rawTrack, offset);
|
|
||||||
if (!result.nibble) { break; }
|
|
||||||
track.push(result.nibble);
|
|
||||||
offset = result.offset + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tracks[trackNo] = new Uint8Array(track);
|
|
||||||
this.rawTracks[trackNo] = new Uint8Array(rawTrack);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MetaChunk(data) {
|
|
||||||
const infoStr = stringFromBytes(data, 0, data.byteLength);
|
|
||||||
const parts = infoStr.split('\n');
|
|
||||||
const info = parts.reduce(function(acc, part) {
|
|
||||||
const subParts = part.split('\t');
|
|
||||||
acc[subParts[0]] = subParts[1];
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
Object.assign(this, info);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a `Disk` object from Woz image data.
|
|
||||||
* @param {*} options the disk image and options
|
|
||||||
* @returns {import('./format_utils').Disk}
|
|
||||||
*/
|
|
||||||
export default function createDiskFromWoz(options) {
|
|
||||||
const { rawData } = options;
|
|
||||||
const dv = new DataView(rawData, 0);
|
|
||||||
let dvOffset = 0;
|
|
||||||
const disk = {
|
|
||||||
format: 'woz',
|
|
||||||
encoding: ENCODING_BITSTREAM,
|
|
||||||
};
|
|
||||||
|
|
||||||
let wozVersion;
|
|
||||||
const chunks = {};
|
|
||||||
|
|
||||||
function readHeader() {
|
|
||||||
const wozSignature = dv.getUint32(WOZ_HEADER_START + 0, true);
|
|
||||||
|
|
||||||
switch (wozSignature) {
|
|
||||||
case WOZ1_SIGNATURE:
|
|
||||||
wozVersion = 1;
|
|
||||||
break;
|
|
||||||
case WOZ2_SIGNATURE:
|
|
||||||
wozVersion = 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dv.getUint32(WOZ_HEADER_START + 4, true) !== WOZ_INTEGRITY_CHECK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readChunk() {
|
|
||||||
if (dvOffset >= dv.byteLength) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = dv.getUint32(dvOffset, true);
|
|
||||||
const size = dv.getUint32(dvOffset + 4, true);
|
|
||||||
const data = new DataView(dv.buffer, dvOffset + 8, size);
|
|
||||||
dvOffset += size + 8;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: type,
|
|
||||||
size: size,
|
|
||||||
data: data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readHeader()) {
|
|
||||||
dvOffset = WOZ_HEADER_SIZE;
|
|
||||||
let chunk = readChunk();
|
|
||||||
while (chunk) {
|
|
||||||
switch (chunk.type) {
|
|
||||||
case 0x4F464E49: // INFO
|
|
||||||
chunks.info = new InfoChunk(chunk.data);
|
|
||||||
break;
|
|
||||||
case 0x50414D54: // TMAP
|
|
||||||
chunks.tmap = new TMapChunk(chunk.data);
|
|
||||||
break;
|
|
||||||
case 0x534B5254: // TRKS
|
|
||||||
if (wozVersion === 1) {
|
|
||||||
chunks.trks = new TrksChunk(chunk.data);
|
|
||||||
} else {
|
|
||||||
chunks.trks = new TrksChunk2(chunk.data);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x4154454D: // META
|
|
||||||
chunks.meta = new MetaChunk(chunk.data);
|
|
||||||
break;
|
|
||||||
case 0x54495257: // WRIT
|
|
||||||
// Ignore
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
debug('Unsupported chunk', toHex(chunk.type, 8));
|
|
||||||
}
|
|
||||||
chunk = readChunk();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(chunks);
|
|
||||||
|
|
||||||
disk.trackMap = chunks.tmap.trackMap;
|
|
||||||
disk.tracks = chunks.trks.tracks;
|
|
||||||
disk.rawTracks = chunks.trks.rawTracks;
|
|
||||||
disk.readOnly = true; //chunks.info.writeProtected === 1;
|
|
||||||
disk.name = chunks.meta?.title || options.name;
|
|
||||||
|
|
||||||
return disk;
|
|
||||||
}
|
|
313
js/formats/woz.ts
Normal file
313
js/formats/woz.ts
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
/* Copyright 2010-2021 Will Scullin <scullin@scullinsteel.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||||
|
* documentation for any purpose is hereby granted without fee, provided that
|
||||||
|
* the above copyright notice appear in all copies and that both that
|
||||||
|
* copyright notice and this permission notice appear in supporting
|
||||||
|
* documentation. No representations are made about the suitability of this
|
||||||
|
* software for any purpose. It is provided "as is" without express or
|
||||||
|
* implied warranty.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { debug, toHex } from '../util';
|
||||||
|
import { bit, byte, word } from '../types';
|
||||||
|
import { grabNibble } from './format_utils';
|
||||||
|
import { DiskOptions, ENCODING_BITSTREAM, WozDisk } from './types';
|
||||||
|
|
||||||
|
const WOZ_HEADER_START = 0;
|
||||||
|
const WOZ_HEADER_SIZE = 12;
|
||||||
|
|
||||||
|
const WOZ1_SIGNATURE = 0x315A4F57;
|
||||||
|
const WOZ2_SIGNATURE = 0x325A4F57;
|
||||||
|
const WOZ_INTEGRITY_CHECK = 0x0a0d0aff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a range of bytes from a DataView into an ASCII string
|
||||||
|
*
|
||||||
|
* @param data DataView containing string
|
||||||
|
* @param start start index of string
|
||||||
|
* @param end end index of string
|
||||||
|
* @returns ASCII string
|
||||||
|
*/
|
||||||
|
|
||||||
|
function stringFromBytes(data: DataView, start: number, end: number): string {
|
||||||
|
return String.fromCharCode.apply(
|
||||||
|
null,
|
||||||
|
new Uint8Array(data.buffer.slice(data.byteOffset + start, data.byteOffset + end))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InfoChunk {
|
||||||
|
version: byte
|
||||||
|
|
||||||
|
// Version 1
|
||||||
|
diskType: byte
|
||||||
|
writeProtected: byte
|
||||||
|
synchronized: byte
|
||||||
|
cleaned: byte
|
||||||
|
creator: string
|
||||||
|
|
||||||
|
// Version 2
|
||||||
|
sides: byte = 0
|
||||||
|
bootSector: byte = 0
|
||||||
|
bitTiming: byte = 0
|
||||||
|
compatibleHardware: word = 0
|
||||||
|
requiredRAM: word = 0
|
||||||
|
largestTrack: word = 0
|
||||||
|
|
||||||
|
constructor(data: DataView) {
|
||||||
|
this.version = data.getUint8(0);
|
||||||
|
this.diskType = data.getUint8(1);
|
||||||
|
this.writeProtected = data.getUint8(2);
|
||||||
|
this.synchronized = data.getUint8(3);
|
||||||
|
this.cleaned = data.getUint8(4);
|
||||||
|
this.creator = stringFromBytes(data, 5, 37);
|
||||||
|
|
||||||
|
if (this.version > 1) {
|
||||||
|
this.sides = data.getUint8(37);
|
||||||
|
this.bootSector = data.getUint8(38);
|
||||||
|
this.bitTiming = data.getUint8(39);
|
||||||
|
this.compatibleHardware = data.getUint16(40, true);
|
||||||
|
this.requiredRAM = data.getUint16(42, true);
|
||||||
|
this.largestTrack = data.getUint16(44, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TMapChunk {
|
||||||
|
trackMap: byte[]
|
||||||
|
|
||||||
|
constructor(data: DataView) {
|
||||||
|
this.trackMap = [];
|
||||||
|
|
||||||
|
for (let idx = 0; idx < 160; idx++) {
|
||||||
|
this.trackMap.push(data.getUint8(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WOZ_TRACK_SIZE = 6656;
|
||||||
|
const WOZ_TRACK_INFO_BITS = 6648;
|
||||||
|
|
||||||
|
export class TrksChunk {
|
||||||
|
rawTracks: Uint8Array[]
|
||||||
|
tracks: Uint8Array[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TrksChunk1 extends TrksChunk {
|
||||||
|
constructor(data: DataView) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.rawTracks = [];
|
||||||
|
this.tracks = [];
|
||||||
|
|
||||||
|
for (let trackNo = 0, idx = 0; idx < data.byteLength; idx += WOZ_TRACK_SIZE, trackNo++) {
|
||||||
|
let track = [];
|
||||||
|
const rawTrack: bit[] = [];
|
||||||
|
const slice = data.buffer.slice(data.byteOffset + idx, data.byteOffset + idx + WOZ_TRACK_SIZE);
|
||||||
|
const trackData = new Uint8Array(slice);
|
||||||
|
const trackInfo = new DataView(slice);
|
||||||
|
const trackBitCount = trackInfo.getUint16(WOZ_TRACK_INFO_BITS, true);
|
||||||
|
for (let jdx = 0; jdx < trackBitCount; jdx++) {
|
||||||
|
const byteIndex = jdx >> 3;
|
||||||
|
const bitIndex = 7 - (jdx & 0x07);
|
||||||
|
rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x01 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
track = [];
|
||||||
|
let offset = 0;
|
||||||
|
while (offset < rawTrack.length) {
|
||||||
|
const result = grabNibble(rawTrack, offset);
|
||||||
|
if (!result.nibble) { break; }
|
||||||
|
track.push(result.nibble);
|
||||||
|
offset = result.offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tracks[trackNo] = new Uint8Array(track);
|
||||||
|
this.rawTracks[trackNo] = new Uint8Array(rawTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Trk {
|
||||||
|
startBlock: word
|
||||||
|
blockCount: word
|
||||||
|
bitCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TrksChunk2 extends TrksChunk {
|
||||||
|
trks: Trk[]
|
||||||
|
|
||||||
|
constructor (data: DataView) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let trackNo;
|
||||||
|
this.trks = [];
|
||||||
|
for (trackNo = 0; trackNo < 160; trackNo++) {
|
||||||
|
const startBlock = data.getUint16(trackNo * 8, true);
|
||||||
|
const blockCount = data.getUint16(trackNo * 8 + 2, true);
|
||||||
|
const bitCount = data.getUint32(trackNo * 8 + 4, true);
|
||||||
|
if (bitCount === 0) { break; }
|
||||||
|
this.trks.push({
|
||||||
|
startBlock: startBlock,
|
||||||
|
blockCount: blockCount,
|
||||||
|
bitCount: bitCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.tracks = [];
|
||||||
|
this.rawTracks = [];
|
||||||
|
|
||||||
|
const bits = data.buffer;
|
||||||
|
for (trackNo = 0; trackNo < this.trks.length; trackNo++) {
|
||||||
|
const trk = this.trks[trackNo];
|
||||||
|
|
||||||
|
let track = [];
|
||||||
|
const rawTrack: bit[] = [];
|
||||||
|
const start = trk.startBlock * 512;
|
||||||
|
const end = start + trk.blockCount * 512;
|
||||||
|
const slice = bits.slice(start, end);
|
||||||
|
const trackData = new Uint8Array(slice);
|
||||||
|
for (let jdx = 0; jdx < trk.bitCount; jdx++) {
|
||||||
|
const byteIndex = jdx >> 3;
|
||||||
|
const bitIndex = 7 - (jdx & 0x07);
|
||||||
|
rawTrack[jdx] = (trackData[byteIndex] >> bitIndex) & 0x01 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
track = [];
|
||||||
|
let offset = 0;
|
||||||
|
while (offset < rawTrack.length) {
|
||||||
|
const result = grabNibble(rawTrack, offset);
|
||||||
|
if (!result.nibble) { break; }
|
||||||
|
track.push(result.nibble);
|
||||||
|
offset = result.offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tracks[trackNo] = new Uint8Array(track);
|
||||||
|
this.rawTracks[trackNo] = new Uint8Array(rawTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class MetaChunk {
|
||||||
|
values: Record<string, string>
|
||||||
|
|
||||||
|
constructor (data: DataView) {
|
||||||
|
const infoStr = stringFromBytes(data, 0, data.byteLength);
|
||||||
|
const parts = infoStr.split('\n');
|
||||||
|
this.values = parts.reduce(function(acc: Record<string, string>, part) {
|
||||||
|
const subParts = part.split('\t');
|
||||||
|
acc[subParts[0]] = subParts[1];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Chunks {
|
||||||
|
[key: string]: any
|
||||||
|
info?: InfoChunk
|
||||||
|
tmap?: TMapChunk
|
||||||
|
trks?: TrksChunk
|
||||||
|
meta?: MetaChunk
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a `Disk` object from Woz image data.
|
||||||
|
* @param {*} options the disk image and options
|
||||||
|
* @returns {import('./format_utils').Disk}
|
||||||
|
*/
|
||||||
|
export default function createDiskFromWoz(options: DiskOptions) {
|
||||||
|
const { rawData } = options;
|
||||||
|
if (!rawData) {
|
||||||
|
throw new Error('Requires rawData');
|
||||||
|
}
|
||||||
|
const dv = new DataView(rawData, 0);
|
||||||
|
let dvOffset = 0;
|
||||||
|
const disk: Partial<WozDisk> = {
|
||||||
|
encoding: ENCODING_BITSTREAM,
|
||||||
|
};
|
||||||
|
|
||||||
|
let wozVersion;
|
||||||
|
const chunks: Chunks = {};
|
||||||
|
|
||||||
|
function readHeader() {
|
||||||
|
const wozSignature = dv.getUint32(WOZ_HEADER_START + 0, true);
|
||||||
|
|
||||||
|
switch (wozSignature) {
|
||||||
|
case WOZ1_SIGNATURE:
|
||||||
|
wozVersion = 1;
|
||||||
|
break;
|
||||||
|
case WOZ2_SIGNATURE:
|
||||||
|
wozVersion = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dv.getUint32(WOZ_HEADER_START + 4, true) !== WOZ_INTEGRITY_CHECK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readChunk() {
|
||||||
|
if (dvOffset >= dv.byteLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = dv.getUint32(dvOffset, true);
|
||||||
|
const size = dv.getUint32(dvOffset + 4, true);
|
||||||
|
const data = new DataView(dv.buffer, dvOffset + 8, size);
|
||||||
|
dvOffset += size + 8;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
size: size,
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readHeader()) {
|
||||||
|
dvOffset = WOZ_HEADER_SIZE;
|
||||||
|
let chunk = readChunk();
|
||||||
|
while (chunk) {
|
||||||
|
switch (chunk.type) {
|
||||||
|
case 0x4F464E49: // INFO
|
||||||
|
chunks.info = new InfoChunk(chunk.data);
|
||||||
|
break;
|
||||||
|
case 0x50414D54: // TMAP
|
||||||
|
chunks.tmap = new TMapChunk(chunk.data);
|
||||||
|
break;
|
||||||
|
case 0x534B5254: // TRKS
|
||||||
|
if (wozVersion === 1) {
|
||||||
|
chunks.trks = new TrksChunk1(chunk.data);
|
||||||
|
} else {
|
||||||
|
chunks.trks = new TrksChunk2(chunk.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x4154454D: // META
|
||||||
|
chunks.meta = new MetaChunk(chunk.data);
|
||||||
|
break;
|
||||||
|
case 0x54495257: // WRIT
|
||||||
|
// Ignore
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debug('Unsupported chunk', toHex(chunk.type, 8));
|
||||||
|
}
|
||||||
|
chunk = readChunk();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('Invalid woz header');
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(chunks);
|
||||||
|
|
||||||
|
disk.trackMap = chunks.tmap?.trackMap || [];
|
||||||
|
disk.tracks = chunks.trks?.tracks || [];
|
||||||
|
disk.rawTracks = chunks.trks?.rawTracks || [];
|
||||||
|
disk.readOnly = true; //chunks.info.writeProtected === 1;
|
||||||
|
disk.name = chunks.meta?.values['title'] || options.name;
|
||||||
|
|
||||||
|
return disk as WozDisk;
|
||||||
|
}
|
221
package-lock.json
generated
221
package-lock.json
generated
|
@ -19,8 +19,8 @@
|
||||||
"@types/jest": "^26.0.14",
|
"@types/jest": "^26.0.14",
|
||||||
"@types/jest-image-snapshot": "^4.3.0",
|
"@types/jest-image-snapshot": "^4.3.0",
|
||||||
"@types/micromodal": "^0.3.2",
|
"@types/micromodal": "^0.3.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.6.1",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
"ajv": "^6.12.0",
|
"ajv": "^6.12.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"canvas": "^2.7.0",
|
"canvas": "^2.7.0",
|
||||||
|
@ -2365,19 +2365,18 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz",
|
||||||
"integrity": "sha512-CRQNQ0mC2Pa7VLwKFbrGVTArfdVDdefS+gTw0oC98vSI98IX5A8EVH4BzJ2FOB0YlCmm8Im36Elad/Jgtvveaw==",
|
"integrity": "sha512-PGqpLLzHSxq956rzNGasO3GsAPf2lY9lDUBXhS++SKonglUmJypaUtcKzRtUte8CV7nruwnDxtLUKpVxs0wQBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/experimental-utils": "4.19.0",
|
"@typescript-eslint/experimental-utils": "4.28.2",
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"lodash": "^4.17.15",
|
"regexpp": "^3.1.0",
|
||||||
"regexpp": "^3.0.0",
|
"semver": "^7.3.5",
|
||||||
"semver": "^7.3.2",
|
"tsutils": "^3.21.0"
|
||||||
"tsutils": "^3.17.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
|
@ -2412,17 +2411,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/experimental-utils": {
|
"node_modules/@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz",
|
||||||
"integrity": "sha512-9/23F1nnyzbHKuoTqFN1iXwN3bvOm/PRIXSBR3qFAYotK/0LveEOHr5JT1WZSzcD6BESl8kPOG3OoDRKO84bHA==",
|
"integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json-schema": "^7.0.3",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.19.0",
|
"@typescript-eslint/typescript-estree": "4.28.2",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^2.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
|
@ -2435,16 +2434,34 @@
|
||||||
"eslint": "*"
|
"eslint": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": {
|
||||||
"version": "4.19.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
|
||||||
"integrity": "sha512-/uabZjo2ZZhm66rdAu21HA8nQebl3lAIDcybUoOxoI7VbZBYavLIwtOOmykKCJy+Xq6Vw6ugkiwn8Js7D6wieA==",
|
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
"@typescript-eslint/types": "4.19.0",
|
},
|
||||||
"@typescript-eslint/typescript-estree": "4.19.0",
|
"engines": {
|
||||||
"debug": "^4.1.1"
|
"node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mysticatea"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser": {
|
||||||
|
"version": "4.28.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.2.tgz",
|
||||||
|
"integrity": "sha512-Q0gSCN51eikAgFGY+gnd5p9bhhCUAl0ERMiDKrTzpSoMYRubdB8MJrTTR/BBii8z+iFwz8oihxd0RAdP4l8w8w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
|
"@typescript-eslint/types": "4.28.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "4.28.2",
|
||||||
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
|
@ -2463,13 +2480,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz",
|
||||||
"integrity": "sha512-GGy4Ba/hLXwJXygkXqMzduqOMc+Na6LrJTZXJWVhRrSuZeXmu8TAnniQVKgj8uTRKe4igO2ysYzH+Np879G75g==",
|
"integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.19.0"
|
"@typescript-eslint/visitor-keys": "4.28.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||||
|
@ -2480,9 +2497,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz",
|
||||||
"integrity": "sha512-A4iAlexVvd4IBsSTNxdvdepW0D4uR/fwxDrKUa+iEY9UWvGREu2ZyB8ylTENM1SH8F7bVC9ac9+si3LWNxcBuA==",
|
"integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||||
|
@ -2493,18 +2510,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz",
|
||||||
"integrity": "sha512-3xqArJ/A62smaQYRv2ZFyTA+XxGGWmlDYrsfZG68zJeNbeqRScnhf81rUVa6QG4UgzHnXw5VnMT5cg75dQGDkA==",
|
"integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.19.0",
|
"@typescript-eslint/visitor-keys": "4.28.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.1",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.5",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.21.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
|
@ -2535,12 +2552,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz",
|
||||||
"integrity": "sha512-aGPS6kz//j7XLSlgpzU2SeTqHPsmRYxFztj2vPuMMFJXZudpRSehE3WCV+BaxwZFvfAqMoSd86TEuM0PQ59E/A==",
|
"integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -15649,19 +15666,18 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz",
|
||||||
"integrity": "sha512-CRQNQ0mC2Pa7VLwKFbrGVTArfdVDdefS+gTw0oC98vSI98IX5A8EVH4BzJ2FOB0YlCmm8Im36Elad/Jgtvveaw==",
|
"integrity": "sha512-PGqpLLzHSxq956rzNGasO3GsAPf2lY9lDUBXhS++SKonglUmJypaUtcKzRtUte8CV7nruwnDxtLUKpVxs0wQBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "4.19.0",
|
"@typescript-eslint/experimental-utils": "4.28.2",
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"lodash": "^4.17.15",
|
"regexpp": "^3.1.0",
|
||||||
"regexpp": "^3.0.0",
|
"semver": "^7.3.5",
|
||||||
"semver": "^7.3.2",
|
"tsutils": "^3.21.0"
|
||||||
"tsutils": "^3.17.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": {
|
"semver": {
|
||||||
|
@ -15676,60 +15692,71 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/experimental-utils": {
|
"@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz",
|
||||||
"integrity": "sha512-9/23F1nnyzbHKuoTqFN1iXwN3bvOm/PRIXSBR3qFAYotK/0LveEOHr5JT1WZSzcD6BESl8kPOG3OoDRKO84bHA==",
|
"integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json-schema": "^7.0.3",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.19.0",
|
"@typescript-eslint/typescript-estree": "4.28.2",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^2.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-utils": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.2.tgz",
|
||||||
"integrity": "sha512-/uabZjo2ZZhm66rdAu21HA8nQebl3lAIDcybUoOxoI7VbZBYavLIwtOOmykKCJy+Xq6Vw6ugkiwn8Js7D6wieA==",
|
"integrity": "sha512-Q0gSCN51eikAgFGY+gnd5p9bhhCUAl0ERMiDKrTzpSoMYRubdB8MJrTTR/BBii8z+iFwz8oihxd0RAdP4l8w8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "4.19.0",
|
"@typescript-eslint/scope-manager": "4.28.2",
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.19.0",
|
"@typescript-eslint/typescript-estree": "4.28.2",
|
||||||
"debug": "^4.1.1"
|
"debug": "^4.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz",
|
||||||
"integrity": "sha512-GGy4Ba/hLXwJXygkXqMzduqOMc+Na6LrJTZXJWVhRrSuZeXmu8TAnniQVKgj8uTRKe4igO2ysYzH+Np879G75g==",
|
"integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.19.0"
|
"@typescript-eslint/visitor-keys": "4.28.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz",
|
||||||
"integrity": "sha512-A4iAlexVvd4IBsSTNxdvdepW0D4uR/fwxDrKUa+iEY9UWvGREu2ZyB8ylTENM1SH8F7bVC9ac9+si3LWNxcBuA==",
|
"integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz",
|
||||||
"integrity": "sha512-3xqArJ/A62smaQYRv2ZFyTA+XxGGWmlDYrsfZG68zJeNbeqRScnhf81rUVa6QG4UgzHnXw5VnMT5cg75dQGDkA==",
|
"integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.19.0",
|
"@typescript-eslint/visitor-keys": "4.28.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.1",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.5",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.21.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": {
|
"semver": {
|
||||||
|
@ -15744,12 +15771,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.19.0",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz",
|
||||||
"integrity": "sha512-aGPS6kz//j7XLSlgpzU2SeTqHPsmRYxFztj2vPuMMFJXZudpRSehE3WCV+BaxwZFvfAqMoSd86TEuM0PQ59E/A==",
|
"integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.19.0",
|
"@typescript-eslint/types": "4.28.2",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,8 +31,8 @@
|
||||||
"@types/jest": "^26.0.14",
|
"@types/jest": "^26.0.14",
|
||||||
"@types/jest-image-snapshot": "^4.3.0",
|
"@types/jest-image-snapshot": "^4.3.0",
|
||||||
"@types/micromodal": "^0.3.2",
|
"@types/micromodal": "^0.3.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.6.1",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
"ajv": "^6.12.0",
|
"ajv": "^6.12.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"canvas": "^2.7.0",
|
"canvas": "^2.7.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import DOS13 from '../../../js/formats/d13';
|
import DOS13 from 'js/formats/d13';
|
||||||
import { D13O } from '../../../js/formats/format_utils';
|
import { D13O } from 'js/formats/format_utils';
|
||||||
import { memory } from '../../../js/types';
|
import { memory } from 'js/types';
|
||||||
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/13sector';
|
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/13sector';
|
||||||
import { expectSequence, findBytes, skipGap } from './util';
|
import { expectSequence, findBytes, skipGap } from './util';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import DOS from '../../../js/formats/do';
|
import DOS from 'js/formats/do';
|
||||||
import { memory } from '../../../js/types';
|
import { memory } from 'js/types';
|
||||||
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/16sector';
|
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/16sector';
|
||||||
import { expectSequence, findBytes, skipGap } from './util';
|
import { expectSequence, findBytes, skipGap } from './util';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import ProDOS from '../../../js/formats/po';
|
import ProDOS from 'js/formats/po';
|
||||||
import { memory } from '../../../js/types';
|
import { memory } from 'js/types';
|
||||||
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/16sector';
|
import { BYTES_BY_SECTOR, BYTES_BY_TRACK } from './testdata/16sector';
|
||||||
import { expectSequence, findBytes, skipGap } from './util';
|
import { expectSequence, findBytes, skipGap } from './util';
|
||||||
|
|
||||||
|
|
2
test/js/formats/testdata/13sector.ts
vendored
2
test/js/formats/testdata/13sector.ts
vendored
|
@ -1,4 +1,4 @@
|
||||||
import { memory } from '../../../../js/types';
|
import { memory } from 'js/types';
|
||||||
|
|
||||||
function generateBytesInOrder() {
|
function generateBytesInOrder() {
|
||||||
const data: memory[][] = [];
|
const data: memory[][] = [];
|
||||||
|
|
2
test/js/formats/testdata/16sector.ts
vendored
2
test/js/formats/testdata/16sector.ts
vendored
|
@ -1,4 +1,4 @@
|
||||||
import { memory } from '../../../../js/types';
|
import { memory } from 'js/types';
|
||||||
|
|
||||||
function generateBytesInOrder() {
|
function generateBytesInOrder() {
|
||||||
const data: memory[][] = [];
|
const data: memory[][] = [];
|
||||||
|
|
169
test/js/formats/testdata/woz.ts
vendored
Normal file
169
test/js/formats/testdata/woz.ts
vendored
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import {
|
||||||
|
numberToBytes,
|
||||||
|
stringToBytes,
|
||||||
|
} from '../util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1 INFO segment
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockInfo1 = [
|
||||||
|
0x01, // Version
|
||||||
|
0x01, // Disk Type (5.25")
|
||||||
|
0x00, // Write protected
|
||||||
|
0x01, // Synchronized
|
||||||
|
0x00, // Cleaned
|
||||||
|
...stringToBytes('Apple2JS', ' ', 32),
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 23 Unused
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 2 INFO segment
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockInfo2 = [
|
||||||
|
0x02, // Version
|
||||||
|
0x01, // Disk Type (5.25")
|
||||||
|
0x00, // Write protected
|
||||||
|
0x01, // Synchronized
|
||||||
|
0x00, // Cleaned
|
||||||
|
...stringToBytes('Apple2JS', ' ', 32),
|
||||||
|
0x01, // sides
|
||||||
|
0x00, // bootSector
|
||||||
|
0x00, // bitTiming
|
||||||
|
0x00, 0x00, // compatibleHardware
|
||||||
|
0x00, 0x00, // requiredRAM
|
||||||
|
0x00, 0x00, // largest track
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 14 unused
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track map all pointing to track 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const mockTMAP = new Array(160);
|
||||||
|
mockTMAP.fill(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One very small track
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 24 bits of track data, padded
|
||||||
|
|
||||||
|
const mockTrackData = new Array(6646);
|
||||||
|
mockTrackData.fill(0);
|
||||||
|
mockTrackData[0] = 0xd5;
|
||||||
|
mockTrackData[1] = 0xaa;
|
||||||
|
mockTrackData[2] = 0x96;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1 TRKS structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockTRKS = [
|
||||||
|
...mockTrackData,
|
||||||
|
...numberToBytes(3, 2), // Number of bytes
|
||||||
|
...numberToBytes(24, 2), // Number of bits
|
||||||
|
...numberToBytes(0xffff, 2), // Splice point
|
||||||
|
0, // Splice nibble
|
||||||
|
0, // Splice bit count
|
||||||
|
...numberToBytes(0, 2), // Reserved
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 2 TRKS structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockTrackMap = new Array(160 * 8);
|
||||||
|
mockTrackMap.fill(0);
|
||||||
|
mockTrackMap[0x00] = 0x03;
|
||||||
|
mockTrackMap[0x01] = 0x00;
|
||||||
|
mockTrackMap[0x02] = 0x01;
|
||||||
|
mockTrackMap[0x03] = 0x00;
|
||||||
|
mockTrackMap[0x04] = 0x18;
|
||||||
|
mockTrackMap[0x07] = 0x00;
|
||||||
|
mockTrackMap[0x08] = 0x00;
|
||||||
|
mockTrackMap[0x09] = 0x00;
|
||||||
|
|
||||||
|
const mockTrackData2 = new Array(512);
|
||||||
|
mockTrackData2.fill(0);
|
||||||
|
mockTrackData2[0] = 0xd5;
|
||||||
|
mockTrackData2[1] = 0xaa;
|
||||||
|
mockTrackData2[2] = 0x96;
|
||||||
|
|
||||||
|
const mockTRKS2 = [
|
||||||
|
...mockTrackMap,
|
||||||
|
...mockTrackData2,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* META structures
|
||||||
|
*/
|
||||||
|
|
||||||
|
const mockMETA1 = 'title\tMock Woz 1';
|
||||||
|
const mockMETA2 = 'title\tMock Woz 2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Woz Version 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const mockWoz1: ArrayBuffer = new Uint8Array([
|
||||||
|
// Header
|
||||||
|
...stringToBytes('WOZ1'),
|
||||||
|
0xff, // 7 bit detection
|
||||||
|
0x0a, 0x0d, 0x0a, // LF detection
|
||||||
|
0x00, 0x00, 0x00, 0x00, // CRC
|
||||||
|
// Info chunk
|
||||||
|
...stringToBytes('INFO'),
|
||||||
|
...numberToBytes(60, 4), // Size
|
||||||
|
...mockInfo1,
|
||||||
|
// TMAP chunk
|
||||||
|
...stringToBytes('TMAP'),
|
||||||
|
...numberToBytes(mockTMAP.length, 4), // Size
|
||||||
|
...mockTMAP,
|
||||||
|
// TRKS chunk
|
||||||
|
...stringToBytes('TRKS'),
|
||||||
|
...numberToBytes(mockTRKS.length, 4), // Size
|
||||||
|
...mockTRKS,
|
||||||
|
// META chunk
|
||||||
|
...stringToBytes('META'),
|
||||||
|
...numberToBytes(mockMETA1.length, 4), // Size
|
||||||
|
...stringToBytes(mockMETA1),
|
||||||
|
]).buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Woz Version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const mockWoz2: ArrayBuffer = new Uint8Array([
|
||||||
|
// Header
|
||||||
|
...stringToBytes('WOZ2'),
|
||||||
|
0xff, // 7 bit detection
|
||||||
|
0x0a, 0x0d, 0x0a, // LF detection
|
||||||
|
0x00, 0x00, 0x00, 0x00, // CRC
|
||||||
|
|
||||||
|
// Info chunk
|
||||||
|
...stringToBytes('INFO'),
|
||||||
|
...numberToBytes(mockInfo2.length, 4), // Size
|
||||||
|
...mockInfo2,
|
||||||
|
// TMAP chunk
|
||||||
|
...stringToBytes('TMAP'),
|
||||||
|
...numberToBytes(mockTMAP.length, 4), // Size
|
||||||
|
...mockTMAP,
|
||||||
|
// TRKS chunk
|
||||||
|
...stringToBytes('TRKS'),
|
||||||
|
...numberToBytes(mockTRKS2.length, 4), // Size
|
||||||
|
...mockTRKS2,
|
||||||
|
// META chunk
|
||||||
|
...stringToBytes('META'),
|
||||||
|
...numberToBytes(mockMETA2.length, 4), // Size
|
||||||
|
...stringToBytes(mockMETA2),
|
||||||
|
]).buffer;
|
|
@ -1,4 +1,4 @@
|
||||||
import { memory } from '../../../js/types';
|
import { memory } from 'js/types';
|
||||||
|
|
||||||
export function skipGap(track: memory, start: number = 0): number {
|
export function skipGap(track: memory, start: number = 0): number {
|
||||||
const end = start + 0x100; // no gap is this big
|
const end = start + 0x100; // no gap is this big
|
||||||
|
@ -40,3 +40,43 @@ export function findBytes(track: memory, bytes: number[], start: number = 0): nu
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an ASCII character string into an array of bytes, with optional
|
||||||
|
* padding.
|
||||||
|
*
|
||||||
|
* @param val ASCII character string
|
||||||
|
* @param pad ASCII character to fill reach padded length
|
||||||
|
* @param padLength padded length
|
||||||
|
* @returns an array of bytes
|
||||||
|
*/
|
||||||
|
export const stringToBytes = (val: string, pad: string = '\0', padLength: number = 0) => {
|
||||||
|
const result = [];
|
||||||
|
let idx = 0;
|
||||||
|
while (idx < val.length) {
|
||||||
|
result.push(val.charCodeAt(idx) & 0x7f);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
while (idx++ < padLength) {
|
||||||
|
result.push(pad.charCodeAt(0) & 0x7f);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number into a little endian array of bytes.
|
||||||
|
*
|
||||||
|
* @param val a number
|
||||||
|
* @param count number of bytes in result
|
||||||
|
* @returns an array of bytes
|
||||||
|
*/
|
||||||
|
export const numberToBytes = (val: number, count: number) => {
|
||||||
|
const result = [];
|
||||||
|
let idx = 0;
|
||||||
|
while (idx < count) {
|
||||||
|
result.push(val & 0xff);
|
||||||
|
val >>= 8;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
91
test/js/formats/woz.spec.ts
Normal file
91
test/js/formats/woz.spec.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { WozDisk, ENCODING_BITSTREAM } from 'js/formats/types';
|
||||||
|
import createDiskFromWoz from 'js/formats/woz';
|
||||||
|
import {
|
||||||
|
mockWoz1,
|
||||||
|
mockWoz2,
|
||||||
|
mockTMAP
|
||||||
|
} from './testdata/woz';
|
||||||
|
|
||||||
|
describe('woz', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(console, 'log');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can parse Woz version 1', () => {
|
||||||
|
const options = {
|
||||||
|
name: 'Unknown',
|
||||||
|
volume: 254,
|
||||||
|
readOnly: true,
|
||||||
|
rawData: mockWoz1
|
||||||
|
};
|
||||||
|
|
||||||
|
const disk = createDiskFromWoz(options) as WozDisk;
|
||||||
|
expect(disk).toEqual({
|
||||||
|
name: 'Mock Woz 1',
|
||||||
|
readOnly: true,
|
||||||
|
encoding: ENCODING_BITSTREAM,
|
||||||
|
trackMap: mockTMAP,
|
||||||
|
rawTracks: [new Uint8Array([
|
||||||
|
1, 1, 0, 1, 0, 1, 0, 1,
|
||||||
|
1, 0, 1, 0, 1, 0, 1, 0,
|
||||||
|
1, 0, 0, 1, 0, 1, 1, 0,
|
||||||
|
])],
|
||||||
|
tracks: [new Uint8Array([0xD5, 0xAA, 0x96])],
|
||||||
|
});
|
||||||
|
expect(console.log).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
info: {
|
||||||
|
bitTiming: 0,
|
||||||
|
bootSector: 0,
|
||||||
|
cleaned: 0,
|
||||||
|
compatibleHardware: 0,
|
||||||
|
creator: 'Apple2JS ',
|
||||||
|
diskType: 1,
|
||||||
|
largestTrack: 0,
|
||||||
|
requiredRAM: 0,
|
||||||
|
sides: 0,
|
||||||
|
synchronized: 1,
|
||||||
|
version: 1,
|
||||||
|
writeProtected: 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can parse Woz version 2', () => {
|
||||||
|
const options = {
|
||||||
|
name: 'Unknown',
|
||||||
|
volume: 254,
|
||||||
|
readOnly: true,
|
||||||
|
rawData: mockWoz2
|
||||||
|
};
|
||||||
|
|
||||||
|
const disk = createDiskFromWoz(options) as WozDisk;
|
||||||
|
expect(disk).toEqual({
|
||||||
|
name: 'Mock Woz 2',
|
||||||
|
readOnly: true,
|
||||||
|
encoding: ENCODING_BITSTREAM,
|
||||||
|
trackMap: mockTMAP,
|
||||||
|
rawTracks: [new Uint8Array([
|
||||||
|
1, 1, 0, 1, 0, 1, 0, 1,
|
||||||
|
1, 0, 1, 0, 1, 0, 1, 0,
|
||||||
|
1, 0, 0, 1, 0, 1, 1, 0,
|
||||||
|
])],
|
||||||
|
tracks: [new Uint8Array([0xD5, 0xAA, 0x96])],
|
||||||
|
});
|
||||||
|
expect(console.log).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
info: {
|
||||||
|
bitTiming: 0,
|
||||||
|
bootSector: 0,
|
||||||
|
cleaned: 0,
|
||||||
|
compatibleHardware: 0,
|
||||||
|
creator: 'Apple2JS ',
|
||||||
|
diskType: 1,
|
||||||
|
largestTrack: 0,
|
||||||
|
requiredRAM: 0,
|
||||||
|
sides: 1,
|
||||||
|
synchronized: 1,
|
||||||
|
version: 2,
|
||||||
|
writeProtected: 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user