mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Add 13 sector disk support to getBinary
(#159)
Before, `getBinary` did not work for 13 sector disks because it assumed that all tracks had 16 sectors. On top of that, `readSector` could not decode sectors with 5 and 3 encoding. And finally, `findSector` couldn't even find sectors with DOS 3.2 address fields. All of these have been fixed and some tests added to make sure that they keep working.
This commit is contained in:
parent
2793c25c9f
commit
e280c3d7b8
@ -37,7 +37,7 @@ import {
|
||||
} from '../formats/create_disk';
|
||||
|
||||
import { toHex } from '../util';
|
||||
import { jsonDecode, jsonEncode, readSector } from '../formats/format_utils';
|
||||
import { jsonDecode, jsonEncode, readSector, _D13O, _DO, _PO } from '../formats/format_utils';
|
||||
|
||||
import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2';
|
||||
import Apple2IO from '../apple2io';
|
||||
@ -842,9 +842,9 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work on WOZ disks
|
||||
rwts(drive: DriveNumber, track: byte, sector: byte) {
|
||||
const curDisk = this.disks[drive];
|
||||
/** Reads the given track and physical sector. */
|
||||
rwts(disk: DriveNumber, track: byte, sector: byte) {
|
||||
const curDisk = this.disks[disk];
|
||||
if (!isNibbleDisk(curDisk)) {
|
||||
throw new Error('Can\'t read WOZ disks');
|
||||
}
|
||||
@ -966,8 +966,17 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
||||
this.callbacks.label(drive, name, side);
|
||||
}
|
||||
|
||||
// TODO(flan): Does not work with WOZ or D13 disks
|
||||
getBinary(drive: DriveNumber, ext?: Exclude<NibbleFormat, 'woz' | 'd13'>): MassStorageData | null {
|
||||
/**
|
||||
* Returns the binary image of the non-WOZ disk in the given drive.
|
||||
* For WOZ disks, this method returns `null`. If the `ext` parameter
|
||||
* is supplied, the returned data will match that format or an error
|
||||
* will be thrown. If the `ext` parameter is not supplied, the
|
||||
* original image format for the disk in the drive will be used. If
|
||||
* the current data on the disk is no longer readable in that format,
|
||||
* an error will be thrown. Using `ext == 'nib'` will always return
|
||||
* an image.
|
||||
*/
|
||||
getBinary(drive: DriveNumber, ext?: Exclude<NibbleFormat, 'woz'>): MassStorageData | null {
|
||||
const curDisk = this.disks[drive];
|
||||
if (!isNibbleDisk(curDisk)) {
|
||||
return null;
|
||||
@ -979,15 +988,26 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
||||
this.sectors * tracks.length * 256;
|
||||
const data = new Uint8Array(len);
|
||||
|
||||
const extension = ext ?? format;
|
||||
ext = ext ?? format;
|
||||
let idx = 0;
|
||||
for (let t = 0; t < tracks.length; t++) {
|
||||
if (ext === 'nib') {
|
||||
data.set(tracks[t], idx);
|
||||
idx += tracks[t].length;
|
||||
} else {
|
||||
for (let s = 0; s < 0x10; s++) {
|
||||
const sector = readSector({ ...curDisk, format: extension }, t, s);
|
||||
let maxSector: SupportedSectors;
|
||||
let sectorMap: typeof _PO | typeof _DO | typeof _D13O;
|
||||
if (ext === 'd13') {
|
||||
maxSector = 13;
|
||||
sectorMap = _D13O;
|
||||
} else {
|
||||
maxSector = 16;
|
||||
sectorMap = format === 'po' ? _PO : _DO;
|
||||
}
|
||||
|
||||
for (let s = 0; s < maxSector; s++) {
|
||||
const _s = sectorMap[s];
|
||||
const sector = readSector({ ...curDisk, format: ext }, t, _s);
|
||||
data.set(sector, idx);
|
||||
idx += sector.length;
|
||||
}
|
||||
@ -995,7 +1015,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
|
||||
}
|
||||
|
||||
return {
|
||||
ext: extension,
|
||||
ext,
|
||||
metadata: { name },
|
||||
data: data.buffer,
|
||||
readOnly,
|
||||
|
@ -3,7 +3,7 @@ import { debug, toHex } from 'js/util';
|
||||
import ApplesoftDump from 'js/applesoft/decompiler';
|
||||
import IntegerBASICDump from 'js/intbasic/decompiler';
|
||||
import { MassStorageData, NibbleDisk } from '../types';
|
||||
import { readSector, writeSector } from '../format_utils';
|
||||
import { readSector, writeSector, _DO } from '../format_utils';
|
||||
|
||||
/** Usual track for VTOC */
|
||||
export const DEFAULT_VTOC_TRACK = 0x11;
|
||||
@ -135,7 +135,7 @@ export class DOS33 {
|
||||
|
||||
/**
|
||||
* Method to read or write a sector, could be overloaded to support other
|
||||
* data types.
|
||||
* data types. This uses the DOS logical to physical sector mapping.
|
||||
*
|
||||
* @param track Track to read/write
|
||||
* @param sector Sector to read/write
|
||||
@ -146,14 +146,14 @@ export class DOS33 {
|
||||
rwts(track: byte, sector: byte, data?: Uint8Array): Uint8Array {
|
||||
if (data) {
|
||||
if (isNibbleDisk(this.disk)) {
|
||||
writeSector(this.disk, track, sector, data);
|
||||
writeSector(this.disk, track, _DO[sector], data);
|
||||
} else {
|
||||
const offset = track * 0x1000 + sector * 0x100;
|
||||
new Uint8Array(this.disk.data).set(data, offset);
|
||||
}
|
||||
} else {
|
||||
if (isNibbleDisk(this.disk)) {
|
||||
data = readSector(this.disk, track, sector);
|
||||
data = readSector(this.disk, track, _DO[sector]);
|
||||
} else {
|
||||
const offset = track * 0x1000 + sector * 0x100;
|
||||
// Slice new array so modifications to apply to original track
|
||||
@ -741,7 +741,7 @@ export class DOS33 {
|
||||
export function isMaybeDOS33(disk: NibbleDisk | MassStorageData) {
|
||||
let data;
|
||||
if (isNibbleDisk(disk)) {
|
||||
data = readSector(disk, DEFAULT_VTOC_TRACK, DEFAULT_VTOC_SECTOR);
|
||||
data = readSector(disk, DEFAULT_VTOC_TRACK, _DO[DEFAULT_VTOC_SECTOR]);
|
||||
} else if (disk.data.byteLength > 0) {
|
||||
data = new Uint8Array(
|
||||
disk.data,
|
||||
|
@ -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, isNibbleDiskFormat } from './types';
|
||||
import { NibbleDisk, ENCODING_NIBBLE, JSONDisk, isNibbleDiskFormat, SupportedSectors } from './types';
|
||||
|
||||
/**
|
||||
* DOS 3.3 Physical sector order (index is physical sector, value is DOS sector).
|
||||
@ -47,14 +47,29 @@ export const _D13O = [
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc
|
||||
] as const;
|
||||
|
||||
const _trans53 = [
|
||||
const TRANS53 = [
|
||||
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba,
|
||||
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb,
|
||||
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef,
|
||||
0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff
|
||||
] as const;
|
||||
|
||||
const _trans62 = [
|
||||
export const DETRANS53 = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, // A8
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x06, // B0
|
||||
0x00, 0x00, 0x07, 0x08, 0x00, 0x09, 0x0A, 0x0B, // B8
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // C0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // C8
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, // D0
|
||||
0x00, 0x00, 0x0E, 0x0F, 0x00, 0x10, 0x11, 0x12, // D8
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // E0
|
||||
0x00, 0x00, 0x13, 0x14, 0x00, 0x15, 0x16, 0x17, // E8
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x19, 0x1A, // F0
|
||||
0x00, 0x00, 0x1B, 0x1C, 0x00, 0x1D, 0x1E, 0x1F, // F8
|
||||
] as const;
|
||||
|
||||
const TRANS62 = [
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
@ -65,7 +80,7 @@ const _trans62 = [
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
] as const;
|
||||
|
||||
export const detrans62 = [
|
||||
export const DETRANS62 = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
@ -194,10 +209,10 @@ export function explodeSector16(volume: byte, track: byte, sector: byte, data: m
|
||||
let last = 0;
|
||||
for (let idx = 0; idx < 0x156; idx++) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans62[last ^ val]);
|
||||
buf.push(TRANS62[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
buf.push(_trans62[last]);
|
||||
buf.push(TRANS62[last]);
|
||||
|
||||
buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB
|
||||
|
||||
@ -298,15 +313,15 @@ export function explodeSector13(volume: byte, track: byte, sector: byte, data: m
|
||||
let last = 0;
|
||||
for (let idx = 0x199; idx >= 0x100; idx--) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans53[last ^ val]);
|
||||
buf.push(TRANS53[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
for (let idx = 0x0; idx < 0x100; idx++) {
|
||||
const val = nibbles[idx];
|
||||
buf.push(_trans53[last ^ val]);
|
||||
buf.push(TRANS53[last ^ val]);
|
||||
last = val;
|
||||
}
|
||||
buf.push(_trans53[last]);
|
||||
buf.push(TRANS53[last]);
|
||||
|
||||
buf = buf.concat([0xde, 0xaa, 0xeb]); // Epilog DE AA EB
|
||||
|
||||
@ -323,24 +338,46 @@ export interface TrackNibble {
|
||||
track: byte;
|
||||
sector: byte;
|
||||
nibble: byte;
|
||||
sectors: SupportedSectors;
|
||||
}
|
||||
|
||||
enum LookingFor {
|
||||
START_OF_FIELD_MARKER_FIRST_NIBBLE,
|
||||
START_OF_FIELD_MARKER_SECOND_NIBBLE,
|
||||
FIELD_TYPE_MARKER,
|
||||
ADDRESS_FIELD,
|
||||
ADDRESS_FIELD_13,
|
||||
DATA_FIELD_6AND2,
|
||||
DATA_FIELD_5AND3,
|
||||
}
|
||||
|
||||
export class FindSectorError extends Error {
|
||||
constructor(track: byte, sector: byte, e: unknown | Error | string) {
|
||||
super(`Error finding track ${track} (${toHex(track)}), sector ${sector} (${toHex(sector)}): `
|
||||
+ (e instanceof Error
|
||||
? `${e.message}`
|
||||
: `${String(e)}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a sector of data from a nibblized disk
|
||||
*
|
||||
* TODO(flan): Does not work on WOZ disks
|
||||
* Finds a sector of data from a nibblized disk. The sector given should be the
|
||||
* "physical" sector number, meaning the one that appears in the address field.
|
||||
* The first sector with the right sector number and data whose checksum matches
|
||||
* is returned. This means that for a dual-boot disk (DOS 3.2 and DOS 3.3),
|
||||
* whichever sector is found first wins.
|
||||
*
|
||||
* @param disk Nibble disk
|
||||
* @param track track number to read
|
||||
* @param sector sector number to read
|
||||
* @returns An array of sector data bytes.
|
||||
* @returns the track, sector, nibble offset, and detected sectors
|
||||
*/
|
||||
export function findSector(disk: NibbleDisk, track: byte, sector: byte): TrackNibble {
|
||||
const _sector = disk.format === 'po' ? _PO[sector] : _DO[sector];
|
||||
let val, state = 0;
|
||||
const cur = disk.tracks[track];
|
||||
let sectors: SupportedSectors = 16;
|
||||
let state = LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
let idx = 0;
|
||||
let retry = 0;
|
||||
const cur = disk.tracks[track];
|
||||
|
||||
function _readNext() {
|
||||
const result = cur[idx++];
|
||||
@ -359,23 +396,42 @@ export function findSector(disk: NibbleDisk, track: byte, sector: byte): TrackNi
|
||||
}
|
||||
let t = 0, s = 0, v = 0, checkSum;
|
||||
while (retry < 4) {
|
||||
let val: byte;
|
||||
switch (state) {
|
||||
case 0:
|
||||
case LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE:
|
||||
val = _readNext();
|
||||
state = (val === 0xd5) ? 1 : 0;
|
||||
state = (val === 0xd5)
|
||||
? LookingFor.START_OF_FIELD_MARKER_SECOND_NIBBLE
|
||||
: LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
break;
|
||||
case 1:
|
||||
case LookingFor.START_OF_FIELD_MARKER_SECOND_NIBBLE:
|
||||
val = _readNext();
|
||||
state = (val === 0xaa) ? 2 : 0;
|
||||
state = (val === 0xaa)
|
||||
? LookingFor.FIELD_TYPE_MARKER
|
||||
: LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
break;
|
||||
case 2:
|
||||
case LookingFor.FIELD_TYPE_MARKER:
|
||||
val = _readNext();
|
||||
state = (val === 0x96) ? 3 : (val === 0xad ? 4 : 0);
|
||||
switch (val) {
|
||||
case 0x96:
|
||||
state = LookingFor.ADDRESS_FIELD;
|
||||
sectors = 16;
|
||||
break;
|
||||
case 0xB5:
|
||||
state = LookingFor.ADDRESS_FIELD;
|
||||
sectors = 13;
|
||||
break;
|
||||
case 0xAD:
|
||||
state = sectors === 16 ? LookingFor.DATA_FIELD_6AND2 : LookingFor.DATA_FIELD_5AND3;
|
||||
break;
|
||||
default:
|
||||
state = LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
}
|
||||
break;
|
||||
case 3: // Address
|
||||
case LookingFor.ADDRESS_FIELD:
|
||||
v = defourXfour(_readNext(), _readNext()); // Volume
|
||||
t = defourXfour(_readNext(), _readNext());
|
||||
s = defourXfour(_readNext(), _readNext());
|
||||
t = defourXfour(_readNext(), _readNext()); // Track
|
||||
s = defourXfour(_readNext(), _readNext()); // Sector
|
||||
checkSum = defourXfour(_readNext(), _readNext());
|
||||
if (checkSum !== (v ^ t ^ s)) {
|
||||
debug('Invalid header checksum:', toHex(v), toHex(t), toHex(s), toHex(checkSum));
|
||||
@ -383,20 +439,20 @@ export function findSector(disk: NibbleDisk, track: byte, sector: byte): TrackNi
|
||||
_skipBytes(3); // Skip footer
|
||||
state = 0;
|
||||
break;
|
||||
case 4: // Data
|
||||
if (s === _sector && t === track) {
|
||||
case LookingFor.DATA_FIELD_6AND2:
|
||||
if (s === sector && t === track) {
|
||||
// Save start of data
|
||||
const nibble = idx;
|
||||
|
||||
// Do checksum on data
|
||||
let last = 0;
|
||||
for (let jdx = 0; jdx < 0x156; jdx++) {
|
||||
last = detrans62[_readNext() - 0x80] ^ last;
|
||||
last = DETRANS62[_readNext() - 0x80] ^ last;
|
||||
}
|
||||
const checkSum = detrans62[_readNext() - 0x80] ^ last;
|
||||
const checkSum = DETRANS62[_readNext() - 0x80] ^ last;
|
||||
// Validate checksum before returning
|
||||
if (!checkSum) {
|
||||
return { track, sector, nibble };
|
||||
return { track, sector, nibble, sectors };
|
||||
} else {
|
||||
debug('Invalid data checksum:', toHex(last), toHex(track), toHex(sector), toHex(checkSum));
|
||||
}
|
||||
@ -404,19 +460,66 @@ export function findSector(disk: NibbleDisk, track: byte, sector: byte): TrackNi
|
||||
}
|
||||
else
|
||||
_skipBytes(0x159); // Skip data, checksum and footer
|
||||
state = 0;
|
||||
state = LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
break;
|
||||
case LookingFor.DATA_FIELD_5AND3:
|
||||
if (s === sector && t === track) {
|
||||
// Save start of data
|
||||
const nibble = idx;
|
||||
|
||||
// Do checksum on data
|
||||
let last = 0;
|
||||
for (let jdx = 0; jdx < 0x19A; jdx++) {
|
||||
last = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
}
|
||||
const checkSum = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
// Validate checksum before returning
|
||||
if (!checkSum) {
|
||||
return { track, sector, nibble, sectors };
|
||||
} else {
|
||||
debug('Invalid data checksum:', toHex(last), toHex(track), toHex(sector), toHex(checkSum));
|
||||
}
|
||||
_skipBytes(3); // Skip footer
|
||||
}
|
||||
else {
|
||||
_skipBytes(0x19A); // Skip data, checksum and footer
|
||||
}
|
||||
state = LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
break;
|
||||
default:
|
||||
state = LookingFor.START_OF_FIELD_MARKER_FIRST_NIBBLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Error(`Unable to locate track ${track}, sector ${sector}`);
|
||||
throw new FindSectorError(track, sector, `too many retries (${retry})`);
|
||||
}
|
||||
|
||||
export class InvalidChecksum extends Error {
|
||||
constructor(expected: byte, received: byte) {
|
||||
super(`Expected: ${toHex(expected)}, received: ${toHex(received)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadSectorError extends Error {
|
||||
constructor(track: byte, sector: byte, e: unknown | Error) {
|
||||
super(`Error reading track ${track} (${toHex(track)}), sector ${sector} (${toHex(sector)}): `
|
||||
+ (e instanceof Error
|
||||
? `${e.message}`
|
||||
: `${String(e)}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sector of data from a nibblized disk
|
||||
* Reads a sector of data from a nibblized disk. The sector given should be the
|
||||
* "physical" sector number, meaning the one that appears in the address field.
|
||||
* Like `findSector`, the first sector with the right sector number and data
|
||||
* whose checksum matches is returned. This means that for a dual-boot disk
|
||||
* (DOS 3.2 and DOS 3.3), whichever sector is found first wins.
|
||||
*
|
||||
* TODO(flan): Does not work on WOZ disks
|
||||
* This does not work for WOZ disks.
|
||||
*
|
||||
* If the given track and sector combination is not found, a `ReadSectorError`
|
||||
* will be thrown.
|
||||
*
|
||||
* @param disk Nibble disk
|
||||
* @param track track number to read
|
||||
@ -425,7 +528,7 @@ export function findSector(disk: NibbleDisk, track: byte, sector: byte): TrackNi
|
||||
*/
|
||||
export function readSector(disk: NibbleDisk, track: byte, sector: byte): Uint8Array {
|
||||
const trackNibble = findSector(disk, track, sector);
|
||||
const { nibble } = trackNibble;
|
||||
const { nibble, sectors } = trackNibble;
|
||||
const cur = disk.tracks[track];
|
||||
|
||||
let idx = nibble;
|
||||
@ -437,25 +540,32 @@ export function readSector(disk: NibbleDisk, track: byte, sector: byte): Uint8Ar
|
||||
return result;
|
||||
};
|
||||
|
||||
try {
|
||||
return sectors === 13 ? readSector13(_readNext) : readSector16(_readNext);
|
||||
} catch (e: unknown) {
|
||||
throw new ReadSectorError(track, sector, e);
|
||||
}
|
||||
}
|
||||
|
||||
function readSector16(_readNext: () => byte): Uint8Array {
|
||||
const data = new Uint8Array(256);
|
||||
const data2 = [];
|
||||
let last = 0;
|
||||
let last: byte = 0;
|
||||
let val;
|
||||
|
||||
for (let jdx = 0x55; jdx >= 0; jdx--) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
val = DETRANS62[_readNext() - 0x80] ^ last;
|
||||
data2[jdx] = val;
|
||||
last = val;
|
||||
}
|
||||
for (let jdx = 0; jdx < 0x100; jdx++) {
|
||||
val = detrans62[_readNext() - 0x80] ^ last;
|
||||
val = DETRANS62[_readNext() - 0x80] ^ last;
|
||||
data[jdx] = val;
|
||||
last = val;
|
||||
}
|
||||
const checkSum = detrans62[_readNext() - 0x80] ^ last;
|
||||
const checkSum = DETRANS62[_readNext() - 0x80] ^ last;
|
||||
if (checkSum) {
|
||||
debug('Invalid data checksum:', toHex(last), toHex(track), toHex(sector), toHex(checkSum));
|
||||
throw new Error(`Unable to read track ${track}, sector ${sector}`);
|
||||
throw new InvalidChecksum(last, checkSum ^ last);
|
||||
}
|
||||
for (let kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) {
|
||||
data[kdx] <<= 1;
|
||||
@ -475,6 +585,49 @@ export function readSector(disk: NibbleDisk, track: byte, sector: byte): Uint8Ar
|
||||
return data;
|
||||
}
|
||||
|
||||
function readSector13(_readNext: () => byte) {
|
||||
const data = new Uint8Array(256);
|
||||
let val: byte;
|
||||
let last: byte = 0;
|
||||
|
||||
// special low 3-bits of 0xFF
|
||||
val = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
last = val;
|
||||
data[0xff] = val & 0b111;
|
||||
|
||||
// expect 0x99 nibbles of packed lower 3-bits in reverse order
|
||||
for (let i = 0x98; i >= 0x00; i--) {
|
||||
val = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
last = val;
|
||||
const off = Math.floor(i / 0x33) + 5 * (0x32 - (i % 0x33));
|
||||
const dOff = 3 + 5 * (0x32 - (i % 0x33));
|
||||
const eOff = 4 + 5 * (0x32 - (i % 0x33));
|
||||
const bit = 2 - Math.floor(i / 0x33);
|
||||
data[off] = (val & 0b11100) >> 2;
|
||||
data[dOff] ^= ((val & 0b00010) >> 1) << bit;
|
||||
data[eOff] ^= (val & 0b1) << bit;
|
||||
}
|
||||
|
||||
// expect 0xFE nibbles of upper 5-bits
|
||||
for (let i = 0; i < 0xFF; i++) {
|
||||
val = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
last = val;
|
||||
const off = Math.floor(i / 0x33) + 5 * (0x32 - (i % 0x33));
|
||||
data[off] ^= val << 3;
|
||||
}
|
||||
|
||||
// and the last special nibble for 0xFF
|
||||
val = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
last = val;
|
||||
data[0xFF] ^= val << 3;
|
||||
|
||||
const checkSum = DETRANS53[_readNext() - 0xA0] ^ last;
|
||||
if (checkSum) {
|
||||
throw new InvalidChecksum(last, checkSum ^ last);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a sector of data from a nibblized disk
|
||||
*
|
||||
@ -515,7 +668,8 @@ export function jsonEncode(disk: NibbleDisk, pretty: boolean): string {
|
||||
data[t] = base64_encode(disk.tracks[t]);
|
||||
} else {
|
||||
for (let s = 0; s < 0x10; s++) {
|
||||
(data[t] as string[])[s] = base64_encode(readSector(disk, t, s));
|
||||
const _sector = disk.format === 'po' ? _PO[s] : _DO[s];
|
||||
(data[t] as string[])[s] = base64_encode(readSector(disk, t, _sector));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
520
test/js/formats/format_utils.spec.ts
Normal file
520
test/js/formats/format_utils.spec.ts
Normal file
@ -0,0 +1,520 @@
|
||||
import createDiskFromDOS13 from 'js/formats/d13';
|
||||
import createDiskFromDOS from 'js/formats/do';
|
||||
import { defourXfour, DO, explodeSector13, findSector, fourXfour, readSector } from 'js/formats/format_utils';
|
||||
import { BYTES_BY_SECTOR as BYTES_BY_SECTOR_13, BYTES_IN_ORDER as BYTES_IN_ORDER_13 } from './testdata/13sector';
|
||||
import { BYTES_BY_SECTOR as BYTES_BY_SECTOR_16 } from './testdata/16sector';
|
||||
|
||||
describe('fourXfour', () => {
|
||||
// d7 d6 d5 d4 d3 d2 d1 d0
|
||||
// => 1 d7 1 d5 1 d3 1 d1
|
||||
// 1 d6 1 d4 1 d2 1 d0
|
||||
|
||||
it('converts 0x00 correctly', () => {
|
||||
// 0000 0000 => 1010 1010, 1010 1010
|
||||
expect(fourXfour(0x00)).toEqual([0b1010_1010, 0b1010_1010]);
|
||||
});
|
||||
|
||||
it('converts 0xff correctly', () => {
|
||||
// 1111 1111 => 1111 1111, 1111 1111
|
||||
expect(fourXfour(0xFF)).toEqual([0b1111_1111, 0b1111_1111]);
|
||||
});
|
||||
|
||||
it('converts 0x55 correctly', () => {
|
||||
// 0101 0101 => 1010 1010, 1111 1111
|
||||
expect(fourXfour(0x55)).toEqual([0b1010_1010, 0b1111_1111]);
|
||||
});
|
||||
|
||||
it('converts 0xAA correctly', () => {
|
||||
// 1010 1010 => 1111 1111, 1010 1010
|
||||
expect(fourXfour(0xAA)).toEqual([0b1111_1111, 0b1010_1010]);
|
||||
});
|
||||
|
||||
it('converts 0xA5 correctly', () => {
|
||||
// 1010 0101 => 1111 1010, 1010 1111
|
||||
expect(fourXfour(0xA5)).toEqual([0b1111_1010, 0b1010_1111]);
|
||||
});
|
||||
|
||||
it('converts 0x5A correctly', () => {
|
||||
// 0101 1010 => 1010 1111, 1111 1010
|
||||
expect(fourXfour(0x5A)).toEqual([0b1010_1111, 0b1111_1010]);
|
||||
});
|
||||
|
||||
it('converts 0xC3 (0b1100_0011) correctly', () => {
|
||||
// 1100 0011 => 1110 1011, 1110 1011
|
||||
expect(fourXfour(0b1100_0011)).toEqual([0b1110_1011, 0b1110_1011]);
|
||||
});
|
||||
|
||||
it('converts 0x3C (0b0011_1100) correctly', () => {
|
||||
// 0011 1100 => 1011 1110, 1011 1110
|
||||
expect(fourXfour(0b0011_1100)).toEqual([0b1011_1110, 0b1011_1110]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defourXfour', () => {
|
||||
it('converts to 0x00 correctly', () => {
|
||||
// 1010 1010, 1010 1010 => 0000 0000
|
||||
expect(defourXfour(0b1010_1010, 0b1010_1010)).toEqual(0x00);
|
||||
});
|
||||
|
||||
it('converts to 0xff correctly', () => {
|
||||
// 1111 1111, 1111 1111 => 1111 1111
|
||||
expect(defourXfour(0b1111_1111, 0b1111_1111)).toEqual(0xFF);
|
||||
});
|
||||
|
||||
it('converts to 0x55 correctly', () => {
|
||||
// 1010 1010, 1111 1111 => 0101 0101
|
||||
expect(defourXfour(0b1010_1010, 0b1111_1111)).toEqual(0x55);
|
||||
});
|
||||
|
||||
it('converts to 0xAA correctly', () => {
|
||||
// 1111 1111, 1010 1010 => 1010 1010
|
||||
expect(defourXfour(0b1111_1111, 0b1010_1010)).toEqual(0xAA);
|
||||
});
|
||||
|
||||
it('converts to 0xA5 correctly', () => {
|
||||
// 1111 1010, 1010 1111 => 1010 0101
|
||||
expect(defourXfour(0b1111_1010, 0b1010_1111)).toEqual(0xA5);
|
||||
});
|
||||
|
||||
it('converts to 0x5A correctly', () => {
|
||||
// 1010 1111, 1111 1010 => 0101 1010
|
||||
expect(defourXfour(0b1010_1111, 0b1111_1010)).toEqual(0x5A);
|
||||
});
|
||||
|
||||
it('converts to 0xC3 (0b1100_0011) correctly', () => {
|
||||
// 1110 1011, 1110 1011 => 1100 0011
|
||||
expect(defourXfour(0b1110_1011, 0b1110_1011)).toEqual(0b1100_0011);
|
||||
});
|
||||
|
||||
it('converts to 0x3C (0b0011_1100) correctly', () => {
|
||||
// 1011 1110, 1011 1110 => 0011 1100
|
||||
expect(defourXfour(0b1011_1110, 0b1011_1110)).toEqual(0b0011_1100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findSector', () => {
|
||||
describe('for a 16 sector DOS disk', () => {
|
||||
it('correctly finds track 0, sector 0', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 0);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(0);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 0, sector 1', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 1);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(1);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 1 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 342 /* data 6 & 2 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 41 /* GAP3 nibbles for track 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 0, sector 2', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 2);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(2);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 2 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 342 /* data 6 & 2 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 41 /* GAP3 nibbles for track 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 0, sector 15', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 15);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(15);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 15 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 342 /* data 6 & 2 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 41 /* GAP3 nibbles for track 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 1, sector 0', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 1, 0);
|
||||
expect(track).toBe(1);
|
||||
expect(sector).toBe(0);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 1, sector 1', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 1, 1);
|
||||
expect(track).toBe(1);
|
||||
expect(sector).toBe(1);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 1 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 342 /* data 6 & 2 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 39 /* GAP3 nibbles for track > 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
|
||||
it('correctly finds track 1, sector 15', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 1, 15);
|
||||
expect(track).toBe(1);
|
||||
expect(sector).toBe(15);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 15 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 342 /* data 6 & 2 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 39 /* GAP3 nibbles for track > 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(16);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 13 sector disk', () => {
|
||||
it('correctly finds track 0, sector 0 of a 13 sector disk', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 0);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(0);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */);
|
||||
expect(sectors).toBe(13);
|
||||
});
|
||||
|
||||
it('correctly finds track 0, sector 1 of a 13 sector disk', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 0, 1);
|
||||
expect(track).toBe(0);
|
||||
expect(sector).toBe(1);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 4 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 410 /* data 5 & 3 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 41 /* GAP3 nibbles for track 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(13);
|
||||
});
|
||||
|
||||
it('correctly finds track 1, sector 6 of a 13 sector disk', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const { nibble, track, sector, sectors } = findSector(disk, 1, 6);
|
||||
expect(track).toBe(1);
|
||||
expect(sector).toBe(6);
|
||||
expect(nibble).toBe(
|
||||
128 /* GAP1 nibbles */
|
||||
+ 11 * (
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
+ 410 /* data 5 & 3 */
|
||||
+ 1 /* checksum nibble */
|
||||
+ 3 /* epilogue nibbles */
|
||||
+ 39 /* GAP3 nibbles for track > 0 */)
|
||||
+ 14 /* Address Field nibbles */
|
||||
+ 5 /* GAP2 nibbles */
|
||||
+ 3 /* prologue nibbles */
|
||||
);
|
||||
expect(sectors).toBe(13);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readSector', () => {
|
||||
describe('for a 16 sector disk', () => {
|
||||
it('correctly reads track 0, sector 0', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const data = readSector(disk, 0, 0);
|
||||
expect(data).toEqual(new Uint8Array(256));
|
||||
});
|
||||
|
||||
it('correctly reads track 0, sector 1', () => {
|
||||
const disk = createDiskFromDOS({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_16,
|
||||
readOnly: true,
|
||||
});
|
||||
const data = readSector(disk, 0, 1);
|
||||
expect(data).toEqual(new Uint8Array(256).fill(DO[1]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 13 sector disk', () => {
|
||||
it('correctly reads track 0, sector 0', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const data = readSector(disk, 0, 0);
|
||||
expect(data).toEqual(new Uint8Array(256));
|
||||
});
|
||||
|
||||
it('correctly reads track 0, sector 1', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_BY_SECTOR_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const data = readSector(disk, 0, 1);
|
||||
expect(data).toEqual(new Uint8Array(256).fill(1));
|
||||
});
|
||||
|
||||
it('correctly reads track 0, sector 0 bytes in order', () => {
|
||||
const disk = createDiskFromDOS13({
|
||||
name: 'Disk by sector',
|
||||
volume: 254,
|
||||
data: BYTES_IN_ORDER_13,
|
||||
readOnly: true,
|
||||
});
|
||||
const data = readSector(disk, 0, 0);
|
||||
const expected = new Uint8Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
expected[i] = i;
|
||||
}
|
||||
expect(data).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('explodeSector13', () => {
|
||||
it('correctly encodes all 1s', () => {
|
||||
const sector = explodeSector13(256, 0, 0, new Uint8Array(256).fill(1));
|
||||
expect(sector[0]).toBe(0xFF);
|
||||
// Address prologue
|
||||
expect(sector[0x80]).toBe(0xD5);
|
||||
expect(sector[0x81]).toBe(0xAA);
|
||||
expect(sector[0x82]).toBe(0xB5);
|
||||
|
||||
// Data prologue
|
||||
expect(sector[0x93]).toBe(0xD5);
|
||||
expect(sector[0x94]).toBe(0xAA);
|
||||
expect(sector[0x95]).toBe(0xAD);
|
||||
|
||||
// Data
|
||||
expect(sector[0x96]).toBe(0xAD); // 01 special low bit of 0xFF
|
||||
expect(sector[0x97]).toBe(0xB7); // C:001 D0:1 E0:1 -> 07 -> 07 ^ 01 -> 06 -> B7
|
||||
expect(sector[0x98]).toBe(0xAB); // G:001 H0:1 I0:1 -> 07 -> 07 ^ 07 -> 00 -> AB
|
||||
expect(sector[0x99]).toBe(0xAB); // J:001 K0:1 L0:1 -> 07 -> 07 ^ 07 -> 00 -> AB
|
||||
for (let i = 0x9A; i <= 0x96 + 0x33; i++) {
|
||||
expect(sector[i]).toBe(0xAB); // same as above
|
||||
}
|
||||
|
||||
expect(sector[0x96 + 0x34]).toBe(0xAF); // B:001 D1:0 E1:0 -> 04 ^ 07 -> 03 -> AF
|
||||
expect(sector[0x96 + 0x35]).toBe(0xAB); // X:001 Y1:0 Z1:0 -> 04 ^ 04 -> 00 -> AB
|
||||
for (let i = 0x96 + 0x36; i <= 0x96 + 0x33 + 0x33; i++) {
|
||||
expect(sector[i]).toBe(0xAB); // same as above
|
||||
}
|
||||
|
||||
// expect(sector[0x98]).toBe(0xAB); // B:001 D1:0 E1:0 -> 04 -> 04 ^ 07 -> 03 -> AF
|
||||
// expect(sector[0x97]).toBe(0xB7); // A:001 D2:0 E2:0 -> 04 -> 04 ^ 02 -> 06 -> B7
|
||||
});
|
||||
});
|
||||
|
||||
describe('test', () => {
|
||||
it('5-bit nibble to data offset', () => {
|
||||
// const off = (i: number) => 0x33 * (i % 5) + (0x32 - Math.floor(i / 5));
|
||||
const off = (i: number) => Math.floor(i / 0x33) + 5 * (0x32 - (i % 0x33));
|
||||
expect(off(0x32)).toBe(0);
|
||||
expect(off(0x31)).toBe(5);
|
||||
expect(off(0x30)).toBe(10);
|
||||
expect(off(0x65)).toBe(1);
|
||||
expect(off(0x64)).toBe(6);
|
||||
expect(off(0x63)).toBe(11);
|
||||
expect(off(0x98)).toBe(2);
|
||||
expect(off(0x97)).toBe(7);
|
||||
expect(off(0x96)).toBe(12);
|
||||
expect(off(0xCB)).toBe(3);
|
||||
expect(off(0xCA)).toBe(8);
|
||||
expect(off(0xC9)).toBe(13);
|
||||
expect(off(0xFE)).toBe(4);
|
||||
expect(off(0xFD)).toBe(9);
|
||||
expect(off(0xFC)).toBe(14);
|
||||
|
||||
const seen = new Set<number>();
|
||||
for (let i = 0; i < 0xFF; i++) {
|
||||
seen.add(off(i));
|
||||
}
|
||||
for (let i = 0; i < 0xFF; i++) {
|
||||
expect(seen).toContain(i);
|
||||
}
|
||||
});
|
||||
it('3-bit nibble to data offset', () => {
|
||||
// const off = 0x33 * (i % 3) + (0x32 - Math.floor(i / 3));
|
||||
// const off = (i: number) => Math.floor(i / 0x33) + 3 * (0x32 - (i % 0x33));
|
||||
const off = (i: number) => Math.floor(i / 0x33) + 5 * (0x32 - (i % 0x33));
|
||||
const dOff = (i: number) => 3 + 5 * (0x32 - (i % 0x33));
|
||||
const eOff = (i: number) => 4 + 5 * (0x32 - (i % 0x33));
|
||||
const bit = (i: number) => 2 - Math.floor(i / 0x33);
|
||||
expect(off(0x32)).toBe(0);
|
||||
expect(dOff(0x32)).toBe(3);
|
||||
expect(eOff(0x32)).toBe(4);
|
||||
expect(bit(0x32)).toBe(2);
|
||||
|
||||
expect(off(0x65)).toBe(1);
|
||||
expect(dOff(0x65)).toBe(3);
|
||||
expect(eOff(0x65)).toBe(4);
|
||||
expect(bit(0x65)).toBe(1);
|
||||
|
||||
expect(off(0x98)).toBe(2);
|
||||
expect(dOff(0x98)).toBe(3);
|
||||
expect(eOff(0x98)).toBe(4);
|
||||
expect(bit(0x98)).toBe(0);
|
||||
|
||||
expect(off(0x31)).toBe(5);
|
||||
expect(dOff(0x31)).toBe(8);
|
||||
expect(eOff(0x31)).toBe(9);
|
||||
|
||||
expect(off(0x64)).toBe(6);
|
||||
expect(dOff(0x64)).toBe(8);
|
||||
expect(eOff(0x64)).toBe(9);
|
||||
|
||||
expect(off(0x97)).toBe(7);
|
||||
expect(dOff(0x97)).toBe(8);
|
||||
expect(eOff(0x97)).toBe(9);
|
||||
|
||||
expect(off(0x30)).toBe(10);
|
||||
expect(dOff(0x30)).toBe(13);
|
||||
expect(eOff(0x30)).toBe(14);
|
||||
|
||||
const seen = new Set<number>();
|
||||
for (let i = 0; i < 0x99; i++) {
|
||||
seen.add(off(i));
|
||||
seen.add(dOff(i));
|
||||
seen.add(eOff(i));
|
||||
}
|
||||
for (let i = 0; i < 0xFF; i++) {
|
||||
expect(seen).toContain(i);
|
||||
}
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user