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