mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
e280c3d7b8
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.
521 lines
19 KiB
TypeScript
521 lines
19 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|