package com.bytezone.diskbrowser.nib; class Nibblizer { // still used by NibDisk and V2dDisk private static byte[] addressPrologue32 = { (byte) 0xD5, (byte) 0xAA, (byte) 0xB5 }; private static byte[] addressPrologue33 = { (byte) 0xD5, (byte) 0xAA, (byte) 0x96 }; private static byte[] dataPrologue = { (byte) 0xD5, (byte) 0xAA, (byte) 0xAD }; private static byte[] epilogue = { (byte) 0xDE, (byte) 0xAA, (byte) 0xEB }; private static int[] interleave = { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; private static final int BLOCK_SIZE = 256; private static final int RAW_BUFFER_SIZE_DOS_33 = 342; private static final int RAW_BUFFER_SIZE_DOS_32 = 410; private static final int BUFFER_WITH_CHECKSUM_SIZE_DOS_33 = RAW_BUFFER_SIZE_DOS_33 + 1; private static final int BUFFER_WITH_CHECKSUM_SIZE_DOS_32 = RAW_BUFFER_SIZE_DOS_32 + 1; private final ByteTranslator byteTranslator62 = new ByteTranslator6and2 (); private final ByteTranslator byteTranslator53 = new ByteTranslator5and3 (); private final byte[] decodeDos33a = new byte[BUFFER_WITH_CHECKSUM_SIZE_DOS_33]; private final byte[] decodeDos33b = new byte[RAW_BUFFER_SIZE_DOS_33]; private final byte[] encodeDos33a = new byte[RAW_BUFFER_SIZE_DOS_33]; private final byte[] encodeDos33b = new byte[BUFFER_WITH_CHECKSUM_SIZE_DOS_33]; private final byte[] decodeDos32a = new byte[BUFFER_WITH_CHECKSUM_SIZE_DOS_32]; private final byte[] decodeDos32b = new byte[RAW_BUFFER_SIZE_DOS_32]; private int sectorsPerTrack; // ---------------------------------------------------------------------------------// // processTrack // ---------------------------------------------------------------------------------// boolean processTrack (int trackNo, int maxTracks, byte[] buffer, byte[] diskBuffer) { int ptr = 0; int totalSectors = 0; boolean[] sectorsFound = new boolean[16]; sectorsPerTrack = maxTracks; try { while (ptr < buffer.length) { if (sectorsPerTrack == 13) ptr = findBytes (buffer, ptr, addressPrologue32); else ptr = findBytes (buffer, ptr, addressPrologue33); if (ptr < 0) { System.out.printf ("Track: %02X - Address prologue not found%n", trackNo); return false; } AddressField addressField = new AddressField (buffer, ptr); if (!addressField.isValid ()) { System.out.printf ("Track: %02X - Invalid address field%n", trackNo); return false; } if (addressField.track != trackNo) { System.out.printf ("Track: %02X - Wrong track found (%02X)%n", trackNo, addressField.track); return false; } if (sectorsFound[addressField.sector]) { System.out.printf ("Track: %02X - Sector already processes (%02X)%n", trackNo, addressField.sector); return false; } sectorsFound[addressField.sector] = true; assert addressField.track == trackNo; ptr += addressField.size (); ptr = findBytes (buffer, ptr, dataPrologue); if (ptr < 0) { System.out.printf ("Track: %02X - Data prologue not found%n", trackNo); return false; } DataField dataField = new DataField (buffer, ptr); if (!dataField.isValid ()) { System.out.printf ("Track: %02X - Invalid data field%n", trackNo); return false; } int offset; if (sectorsPerTrack == 13) offset = addressField.track * 0x0D00 + addressField.sector * BLOCK_SIZE; else offset = addressField.track * 0x1000 + interleave[addressField.sector] * BLOCK_SIZE; System.arraycopy (dataField.dataBuffer, 0, diskBuffer, offset, BLOCK_SIZE); if (++totalSectors == sectorsPerTrack) break; ptr += dataField.size (); } } catch (Exception e) { e.printStackTrace (); } if (totalSectors != sectorsPerTrack) { System.out.printf ("Track: %02X - Sectors found: %02X%n", trackNo, totalSectors); return false; } return true; } // ---------------------------------------------------------------------------------// // decode4and4 // ---------------------------------------------------------------------------------// private int decode4and4 (byte[] buffer, int offset) { int odds = ((buffer[offset] & 0xFF) << 1) + 1; int evens = buffer[offset + 1] & 0xFF; return odds & evens; } // ---------------------------------------------------------------------------------// // decode5and3 // ---------------------------------------------------------------------------------// private byte[] decode5and3 (byte[] buffer, int offset) { // rearrange 410 bytes into 256 byte[] decodedBuffer = new byte[BLOCK_SIZE]; // 256 bytes try { // convert legal disk values to actual 5 bit values for (int i = 0; i < BUFFER_WITH_CHECKSUM_SIZE_DOS_32; i++) // 411 bytes { // System.out.printf ("%,5d %02X%n", i, buffer[offset]); decodeDos32a[i] = (byte) (byteTranslator53.decode (buffer[offset++]) << 3); } // reconstruct 410 bytes each with 5 bits byte chk = 0; int ptr = 0; for (int i = 409; i >= 256; i--) // 154 bytes chk = decodeDos32b[i] = (byte) (decodeDos32a[ptr++] ^ chk); for (int i = 0; i < 256; i++) // 256 bytes chk = decodeDos32b[i] = (byte) (decodeDos32a[ptr++] ^ chk); assert (chk ^ decodeDos32a[ptr]) == 0; byte[] k = new byte[8]; ptr = 0; final int[] lines = { 0, 51, 102, 153, 204, 256, 307, 358 }; // 255 is skipped // process 8 disk bytes at a time, giving 5 valid bytes // do this 51 times, giving 255 bytes for (int i = 50; i >= 0; i--) { for (int j = 0; j < 8; j++) k[j] = decodeDos32b[i + lines[j]]; k[0] |= (k[5] & 0xE0) >>> 5; k[1] |= (k[6] & 0xE0) >>> 5; k[2] |= (k[7] & 0xE0) >>> 5; k[3] |= (k[5] & 0x10) >>> 2; k[3] |= (k[6] & 0x10) >>> 3; k[3] |= (k[7] & 0x10) >>> 4; k[4] |= (k[5] & 0x08) >>> 1; k[4] |= (k[6] & 0x08) >>> 2; k[4] |= (k[7] & 0x08) >>> 3; for (int j = 0; j < 5; j++) decodedBuffer[ptr++] = k[j]; } // add last byte decodedBuffer[255] = (byte) (decodeDos32b[255] | (decodeDos32b[409] >>> 3)); } catch (Exception e) { } return decodedBuffer; } // ---------------------------------------------------------------------------------// // decode6and2 // ---------------------------------------------------------------------------------// private byte[] decode6and2 (byte[] buffer, int offset) { // rearrange 342 bytes into 256 byte[] decodedBuffer = new byte[BLOCK_SIZE]; // 256 bytes try { // convert legal disk values to actual 6 bit values for (int i = 0; i < BUFFER_WITH_CHECKSUM_SIZE_DOS_33; i++) // 343 bytes decodeDos33a[i] = (byte) (byteTranslator62.decode (buffer[offset++]) << 2); // reconstruct 342 bytes each with 6 bits byte chk = 0; for (int i = decodeDos33b.length - 1; i >= 0; i--) // 342 bytes chk = decodeDos33b[i] = (byte) (decodeDos33a[i + 1] ^ chk); assert (chk ^ decodeDos33a[0]) == 0; // move 6 bits into place for (int i = 0; i < BLOCK_SIZE; i++) decodedBuffer[i] = decodeDos33b[i + 86]; // reattach each byte's last 2 bits for (int i = 0, j = 86, k = 172; i < 86; i++, j++, k++) { byte val = decodeDos33b[i]; decodedBuffer[i] |= reverse ((val & 0x0C) >> 2); decodedBuffer[j] |= reverse ((val & 0x30) >> 4); if (k < BLOCK_SIZE) decodedBuffer[k] |= reverse ((val & 0xC0) >> 6); } } catch (Exception e) { } return decodedBuffer; } // ---------------------------------------------------------------------------------// // encode6and2 // ---------------------------------------------------------------------------------// // convert 256 data bytes into 342 translated bytes plus a checksum private byte[] encode6and2 (byte[] buffer) { byte[] encodedBuffer = new byte[BUFFER_WITH_CHECKSUM_SIZE_DOS_33]; // move data buffer down to make room for the 86 extra bytes for (int i = 0; i < BLOCK_SIZE; i++) encodeDos33a[i + 86] = buffer[i]; // build extra 86 bytes from the bits stripped from the data bytes for (int i = 0; i < 86; i++) { int b1 = reverse (buffer[i] & 0x03) << 2; int b2 = reverse (buffer[i + 86] & 0x03) << 4; if (i < 84) { int b3 = reverse (buffer[i + 172] & 0x03) << 6; encodeDos33a[i] = (byte) (b1 | b2 | b3); } else encodeDos33a[i] = (byte) (b1 | b2); } // convert into checksum bytes byte checksum = 0; for (int i = 0; i < RAW_BUFFER_SIZE_DOS_33; i++) { encodeDos33b[i] = (byte) (checksum ^ encodeDos33a[i]); checksum = encodeDos33a[i]; } encodeDos33b[RAW_BUFFER_SIZE_DOS_33] = checksum; // add checksum to the end // remove two bits and convert to translated bytes for (int i = 0; i < BUFFER_WITH_CHECKSUM_SIZE_DOS_33; i++) // encodedBuffer[i] = writeTranslateTable6and2[(encodeDos33b[i] & 0xFC) / 4]; encodedBuffer[i] = byteTranslator62.encode (encodeDos33b[i]); return encodedBuffer; } // ---------------------------------------------------------------------------------// // reverse // ---------------------------------------------------------------------------------// // reverse 2 bits - 0 <= bits <= 3 private static int reverse (int bits) { return bits == 1 ? 2 : bits == 2 ? 1 : bits; } // ---------------------------------------------------------------------------------// // listBytes // ---------------------------------------------------------------------------------// private String listBytes (byte[] buffer, int offset, int length) { StringBuilder text = new StringBuilder (); int max = Math.min (length + offset, buffer.length); while (offset < max) text.append (String.format ("%02X ", buffer[offset++])); return text.toString (); } // ---------------------------------------------------------------------------------// // findBytes // ---------------------------------------------------------------------------------// static int findBytes (byte[] buffer, int offset, byte[] valueBuffer) { while (offset + valueBuffer.length <= buffer.length) { if (matchBytes (buffer, offset, valueBuffer)) return offset; ++offset; } return -1; } // ---------------------------------------------------------------------------------// // matchBytes // ---------------------------------------------------------------------------------// private static boolean matchBytes (byte[] buffer, int offset, byte[] valueBuffer) { if ((buffer.length - offset) < valueBuffer.length) return false; int ptr = 0; try { while (ptr < valueBuffer.length) if (buffer[offset++] != valueBuffer[ptr++]) return false; } catch (ArrayIndexOutOfBoundsException e) { System.out.println ("Error in matchBytes"); e.printStackTrace (); return false; } return true; } // ---------------------------------------------------------------------------------// // Field // ---------------------------------------------------------------------------------// private abstract class Field { protected boolean valid; protected byte[] buffer; protected int offset; protected int length; public Field (byte[] buffer, int offset) { this.buffer = buffer; this.offset = offset; } public boolean isValid () { return valid; } public int size () { assert length > 0; return length; } @Override public String toString () { return String.format ("[Offset: %04X, Length: %04X]", offset, length); } } // ---------------------------------------------------------------------------------// // AddressField // ---------------------------------------------------------------------------------// private class AddressField extends Field { int track, sector, volume, checksum; public AddressField (byte[] buffer, int offset) { super (buffer, offset); volume = decode4and4 (buffer, offset + 3); track = decode4and4 (buffer, offset + 5); sector = decode4and4 (buffer, offset + 7); checksum = decode4and4 (buffer, offset + 9); valid = true; length = 14; } @Override public String toString () { return String.format ("[volume: %02X, track: %02X, sector: %02X, checksum: %02X]", volume, track, sector, checksum); } } // ---------------------------------------------------------------------------------// // DataField // ---------------------------------------------------------------------------------// private class DataField extends Field { byte[] dataBuffer; public DataField (byte[] buffer, int offset) { super (buffer, offset); if (matchBytes (buffer, offset, dataPrologue)) { valid = true; if (sectorsPerTrack == 13) dataBuffer = decode5and3 (buffer, offset + 3); else dataBuffer = decode6and2 (buffer, offset + 3); } else { System.out.print (" bad data prologue: "); System.out.println (listBytes (buffer, offset, 3)); } length = 349; } } }