package com.dreamfabric.jac64; /** * Describe class C1541Chips here. * * * Created: Tue Aug 01 13:08:47 2006 * * @author Joakim Eriksson * @version 1.0 GCR Coding Table Nybble GCR 0 0 0000 01010 a 10 1 1 0001 01011 b 11 2 2 0010 10010 12 18 3 3 0011 10011 13 19 4 4 0100 01110 e 14 5 5 0101 01111 f 15 6 6 0110 10110 16 22 7 7 0111 10111 17 23 8 8 1000 01001 9 9 9 9 1001 11001 19 25 a 10 1010 11010 1a 26 b 11 1011 11011 1b 27 c 12 1100 01101 d 13 d 13 1101 11101 1d 29 e 14 1110 11110 1e 30 f 15 1111 10101 15 21 */ public class C1541Chips extends ExtChip implements DiskListener { // GCR_SECTOR_SIZE => 354 public static final int GCR_SECTOR_SIZE = 1 + 10 + 9 + 1 + 325 + 8; public static final boolean DEBUG = false; public static final boolean DEBUG_IRQ = false; public static final boolean DEBUG_WRITE = false; public static final boolean DEBUG_GCR = false; public static final boolean DEBUG_IEC = false; public static final Object LED_MOTOR = new Object(); public static final Object HEAD_MOVED = new Object(); public static final Object SECTOR_UPDATE = new Object(); // GCR conversion table - used for converting ordinary byte to 10-bits // (or 4 bits to 5) public static final int[] GCR = new int[] { 0x0a, 0x0b, 0x12, 0x13, 0x0e, 0x0f, 0x16, 0x17, 0x09, 0x19, 0x1a, 0x1b, 0x0d, 0x1d, 0x1e, 0x15 }; // 5 bits > 4 bits (0xff => invalid) public static final int[] GCR_REV = new int[] { 0xff, 0xff, 0xff, 0xff, // 0 - 3invalid... 0xff, 0xff, 0xff, 0xff, // 4 - 7 invalid... 0xff, 0x08, 0x00, 0x01, // 8 invalid... 9 = 8, a = 0, b = 1 0xff, 0x0c, 0x04, 0x05, // c invalid... d = c, e = 4, f = 5 0xff, 0xff, 0x02, 0x03, // 10-11 invalid... 0xff, 0x0f, 0x06, 0x07, // 14 invalid... 0xff, 0x09, 0x0a, 0x0b, // 18 invalid... 0xff, 0x0d, 0x0e, 0xff, // 1c, 1f invalid... }; private C1541Emu cpu; private int diskID1 = 0; private int diskID2 = 0; private int track = 1; private int hTrack = 2; private int sector = 0; private int sectorPos = 0; private int currentTrackSize = 21; // The current GCR sector private int[] gcrSector = new int[GCR_SECTOR_SIZE]; private int[] gcrWriteSector = new int[GCR_SECTOR_SIZE]; // Max 40 tracks each 21 sectors private int[][][] gcrCacheSector = new int[40][21][]; private int via1PB; private int via1PA; private int via1CB; private int via1CA; private int via1T1Ctr; private int via1T1Latch; private int via1T2Ctr; private int via1T2Latch; private int via1SerialRegister; private int via1AuxControl; private int via1PerControl; private int via1IFlag; private int via1IEnable; private int via2PB; private int via2PA; private int via2CB; private int via2CA; private int via2T1Ctr; private int via2T1Latch; private int via2T2Ctr; private int via2T2Latch; private int via2SerialRegister; private int via2AuxControl; int via2PerControl; // Needed by the C1541Emu private int via2IFlag; private int via2IEnable; private boolean diskChanged = true; private boolean writeProtected = true; public boolean ledOn; public boolean motorOn; public int currentTrack; public int currentSector; public int headOutBeyond = 0; private int bytesWritten = 0; private int currentByte = 0; private C64Reader reader; boolean byteReadyOverflow = false; boolean diskModeWrite = false; C64Screen cia2; int iecLines; public C1541Chips(C1541Emu emu) { cpu = emu; init(cpu); } public void initIEC2(C64Screen s) { cia2 = s; } public void setReader(C64Reader reader) { log("Setting reader..."); this.reader = reader; reader.setDiskListener(this); } public final int performRead(int address, long cycles) { switch (address) { case 0x1800: // VIA1 PB - IEEE Serial Bus / IEC return (via1PB & 0x1a | ((iecLines & cia2.iecLines) >> 7) & 0x01 // DATA | ((iecLines & cia2.iecLines) >> 4) & 0x04 // CLK | (cia2.iecLines << 3) & 0x80) ^ 0x85; // ATN case 0x1801: // VIA1 PA case 0x180f: // VIA1 PA via1IFlag &= ~2; checkInterrupt(1, "read from pa"); return 0xff; // via1PA; case 0x1802: // VIA1 CB return via1CB; case 0x1803: // VIA1 CA return via1CA; case 0x1804: via1IFlag &= 0xbf; checkInterrupt(1, "read T1 low"); return via1T1Ctr & 0xff; case 0x1805: return via1T1Ctr >> 8; case 0x1806: return via1T1Latch & 0xff; case 0x1807: return via1T1Latch >> 8; case 0x1808: via1IFlag &= 0xdf; checkInterrupt(1, "read T2 low"); return via1T2Ctr & 0xff; case 0x1809: return via1T2Ctr >> 8; case 0x180a: return via1SerialRegister; case 0x180b: return via1AuxControl; case 0x180c: return via1PerControl; case 0x180d: return via1IFlag; case 0x180e: return via1IEnable; case 0x1c00: // VIA2 PB return (via2PB & 0x6f) | sync() | writeProtect(); case 0x1c01: // VIA2 PA - read from disk! case 0x1c0f: return readByte(); case 0x1c02: // VIA2 CB return via2CB; case 0x1c03: // VIA2 CA return via2CA; case 0x1c04: via2IFlag &= 0xbf; checkInterrupt(1, "read T1 low"); return via2T1Ctr & 0xff; case 0x1c05: return via2T1Ctr >> 8; case 0x1c06: return via2T1Latch & 0xff; case 0x1c07: return via2T1Latch >> 8; case 0x1c08: via2IFlag &= 0xdf; // System.out.println("Updated via2IFlag: " + // Integer.toHexString(via2IFlag)); checkInterrupt(2, "read T2 low"); return via2T2Ctr & 0xff; case 0x1c09: return via2T2Ctr >> 8; case 0x1c0a: return via2SerialRegister; case 0x1c0b: return via2AuxControl; case 0x1c0c: return via2PerControl; case 0x1c0d: return via2IFlag; case 0x1c0e: return via2IEnable; } return 0; } public final void performWrite(int address, int data, long cycles) { if (DEBUG_WRITE) { log("Writing to " + Integer.toHexString(address) + " = " + Integer.toHexString(data) + " pc = " + Integer.toHexString(cpu.pc)); } switch (address) { // VIA 1 case 0x1800: // VIA1 PB via1PB = data; // Frodo style (another test...) updateIECLines(); break; case 0x1801: // VIA1 PA // Clear flag... via1IFlag &= ~2; checkInterrupt(1, "wrote pa"); via1PA = data; break; case 0x1802: // VIA1 CB via1CB = data; updateIECLines(); break; case 0x1803: // VIA1 CA via1CA = data; break; case 0x1804: via1T1Latch = (via1T1Latch & 0xff00) | data; break; case 0x1805: // T1 High // 'Reset' timer 1 via1T1Latch = (via1T1Latch & 0xff) | (data << 8); via1IFlag &= 0xbf; via1T1Ctr = via1T1Latch; checkInterrupt(1, "write T1 high"); break; case 0x1806: via1T1Latch = (via1T1Latch & 0xff00) | data; break; case 0x1807: via1T1Latch = (via1T1Latch & 0xff) | (data << 8); break; case 0x1808: via1T2Latch = (via1T2Latch & 0xff00) | data; break; case 0x1809: // T2 high // 'Reset' timer 2 via1T2Latch = (via1T2Latch & 0xff) | (data << 8); via1IFlag &= 0xdf; via1T2Ctr = via1T1Latch; checkInterrupt(1, "write T2 high"); break; case 0x180a: via1SerialRegister = data; break; case 0x180b: via1AuxControl = data; break; case 0x180c: via1PerControl = data; break; case 0x180d: via1IFlag &= ~data; checkInterrupt(1, "write IFlag"); break; case 0x180e: if ((data & 0x80) == 0x80) via1IEnable |= data & 0x7f; else via1IEnable &= ~data; if (DEBUG_WRITE) System.out.println("Wrote IE CIA1: " + via1IEnable); checkInterrupt(1, "write IE"); break; // VIA 2 case 0x1c00: // VIA2 PB boolean lastLed = ledOn; boolean lastMotor = motorOn; ledOn = ((data & 0x08) != 0); motorOn = ((data & 0x04) != 0); if (lastLed != ledOn | lastMotor != motorOn) { update(this, LED_MOTOR); } // System.out.println("C1541: LedON: " + ledOn); // If step motor value changed - check if it is out or in! if (((via2PB ^ data) & 0x3) != 0) { if ((via2PB & 0x3) == ((data + 1) & 0x3)) headOut(); else if ((via2PB & 0x3) == ((data - 1) & 0x3)) headIn(); } via2PB = data; break; case 0x1c01: // VIA2 PA via2PA = data; writeByte(data); break; case 0x1c02: // VIA2 CB via2CB = data; break; case 0x1c03: // VIA2 CA via2CA = data; break; case 0x1c04: via2T1Latch = (via2T1Latch & 0xff00) | data; break; case 0x1c05: // 'Reset' timer 1 via2T1Latch = (via2T1Latch & 0xff) | (data << 8); via2IFlag &= 0xbf; // System.out.println("Updated via2IFlag: " + // Integer.toHexString(via2IFlag)); via2T1Ctr = via2T1Latch; checkInterrupt(2, "write T1 high: " + data + " latch: " + via2T1Latch); // System.out.println("C1541: Wrote via2 T1H(05) Latch: " + data + " => " + // via2T1Latch); break; case 0x1c06: via2T1Latch = (via2T1Latch & 0xff00) | data; // System.out.println("C1541: Wrote via2 T1L Latch: " + data + " => " + // via2T1Latch); break; case 0x1c07: via2T1Latch = (via2T1Latch & 0xff) | (data << 8); // System.out.println("C1541: Wrote via2 T1H(07) Latch: " + data + " => " + // via2T1Latch); break; case 0x1c08: via2T2Latch = (via2T2Latch & 0xff00) | data; break; case 0x1c09: // 'Reset' timer 2 via2T2Latch = (via2T2Latch & 0xff) | (data << 8); via2IFlag &= 0xdf; // System.out.println("Updated via2IFlag: " + // Integer.toHexString(via2IFlag)); via2T2Ctr = via2T2Latch; checkInterrupt(2, "write T2 high"); break; case 0x1c0a: via2SerialRegister = data; break; case 0x1c0b: via2AuxControl = data; break; case 0x1c0c: byteReadyOverflow = (data & 2) == 2; diskModeWrite = (data & 0x20) != 0x20; // System.out.println("Writing to VIA2 PC/CA:" + // Integer.toString(data, 16) + " ByteOverflow: " + // byteReadyOverflow + " write: " + diskModeWrite); via2PerControl = data; if (!diskModeWrite) currentByte = -1; break; case 0x1c0d: via2IFlag &= ~data; // System.out.println("Updated via2IFlag: " + // Integer.toHexString(via2IFlag)); checkInterrupt(2, "write IFlag"); break; case 0x1c0e: if ((data & 0x80) == 0x80) via2IEnable |= data & 0x7f; else via2IEnable &= ~data; checkInterrupt(2, "write IE"); break; } } private void writeByte(int data) { currentByte = data; log("CPU Writes byte: " + Integer.toString(data, 16) + " at " + Integer.toString(cpu.pc, 16)); // forward(); } private void finishWrite() { if (DEBUG) {log("Written bytes: " + bytesWritten + " at " + track + "," + sector + " " + sectorPos); } if (bytesWritten > 0) convertGCRSector(); bytesWritten = 0; } private void checkInterrupt(int via, String reason) { if (via == 1) { if ((via1IFlag & via1IEnable) == 0) { if (DEBUG_IRQ && (via1IFlag & 0x80) != 0) { System.out.println("C1541: ** IRQ/IEC ** Clearing IRQ CIA 1: " + reason); } via1IFlag &= 0x7f; clearIRQ(1); } else { if (DEBUG_IRQ && (via1IFlag & 0x80) == 0) { System.out.println("C1541: ** IRQ/IEC ** Setting IRQ CIA 1: " + reason + " iflag: " + Integer.toHexString(via1IFlag | 0x80)); } via1IFlag |= 0x80; setIRQ(1); } } else { if ((via2IFlag & via2IEnable) == 0) { if (DEBUG_IRQ && (via2IFlag & 0x80) != 0) { System.out.println("C1541: ** IRQ ** Clearing IRQ CIA 2: " + reason); } via2IFlag &= 0x7f; clearIRQ(2); } else { if (DEBUG_IRQ && (via2IFlag & 0x80) == 0) { System.out.println("C1541: ** IRQ ** Setting IRQ CIA 2: " + reason + " iflag: " + Integer.toHexString(via2IFlag | 0x80)); } via2IFlag |= 0x80; setIRQ(2); } } } public void updateIECLines() { int data = ~via1PB & via1CB; iecLines = (data << 6) & ((~data ^ cia2.iecLines) << 3) & 0x80 | (data << 3) & 0x40; } long nextAutoforward; void autoForward(long cycles) { if (motorOn) { if (nextAutoforward < cycles) { if (nextAutoforward == 0) { nextAutoforward = cycles + 32; } else { // 30 seems to work (3 x 8 is fastest, 6x8 slowest) nextAutoforward += 30; forward(); } } } else { nextAutoforward = cycles + 10000; } } // ------------------------------------------------------------------- // Emulation of VIA (update of timers, etc) // ------------------------------------------------------------------- long lastCycles = 0; long nextCheck = 0; public final void clock(long cycles) { if (nextCheck > cycles) return; autoForward(cycles); int delta = (int) (cycles - lastCycles); lastCycles = cycles; nextCheck = cycles + 13; via1T1Ctr = via1T1Ctr - delta; if (via1T1Ctr <= 0) { if ((via1AuxControl & 0x40) == 0x40) { // Increase by latch (to get correct square timing even if not // called each cycle! via1T1Ctr += via1T1Latch; } else { // Otherwise wrap around... via1T1Ctr &= 0xffff; } via1IFlag |= 0x40; checkInterrupt(1, "clock wrap, T1"); } // Only count in one shot more? if ((via1AuxControl & 0x20) == 0) { via1T2Ctr = via1T2Ctr - delta; if (via1T2Ctr <= 0) { via1IFlag |= 0x20; via1T2Ctr &= 0xffff; checkInterrupt(1, "clock wrap, T2"); } } // via 2 via2T1Ctr = via2T1Ctr - delta; if (via2T1Ctr <= 0) { if ((via2AuxControl & 0x40) == 0x40) { // Increase by latch (to get correct square timing even if not // called each cycle! via2T1Ctr += via2T1Latch; } else { // Otherwise wrap around... via2T1Ctr &= 0xffff; } via2IFlag |= 0x40; checkInterrupt(2, "clock wrap, T1: latch:" + via2T1Latch); } // Only count in one shot more? if ((via2AuxControl & 0x20) == 0) { via2T2Ctr = via2T2Ctr - delta; if (via2T2Ctr <= 0) { via2IFlag |= 0x20; via2T2Ctr &= 0xffff; checkInterrupt(2, "clock wrap, T2"); } } } // ------------------------------------------------------------------- // DiskListener // ------------------------------------------------------------------- public void diskChanged() { // reset(); diskChanged = true; // Clear cache... for (int i = 0, n = 40; i < n; i++) { for (int j = 0, m = 21; j < m; j++) { gcrCacheSector[i][j] = null; } } byte[] bam = reader.getSector(18, 0); diskID1 = bam[162] & 0xff; diskID2 = bam[163] & 0xff; System.out.println("Disk changed => Disk ID:" + diskID1 + "," + diskID2); } // Set the via1IFlag for CA1 public void atnChanged(boolean hi) { if (DEBUG_IEC) { System.out.println("C1541: *** IEC ATN changed detected!!! Enable: " + Integer.toHexString(via1IEnable)); } if (hi) { via1IFlag |= 0x02; checkInterrupt(1, "atn went high"); // cpu.memory[0x7c] = 1; } // Recalculate the iecLines... updateIECLines(); } public void reset() { track = 1; hTrack = 2; sector = 0; sectorPos = -1; diskChanged = false; writeProtected = false; currentTrackSize = C64Reader.getSectorCount(track); // Read the current track/sector... readGCRSector(track, sector); } // ------------------------------------------------------------------- // // ------------------------------------------------------------------- private int writeProtect() { if (diskChanged) { // Disk change -> WP sensor strobe System.out.println("C1541: //// Disk change detected???!!! ////"); diskChanged = false; // sectorPos = 0; //(int) (Math.random() * 100); return writeProtected ? 0x10 : 0; } else { return writeProtected ? 0 : 0x10; } } private int sync() { // System.out.println("C1541: Checking Sync: sp: " + sectorPos + " = " + // Integer.toHexString(gcrSector[sectorPos]) + " " + // cpu.cycles); // Sync byte on first sector position! if (sectorPos == -1) return 0x80; if (gcrSector[sectorPos] == 0xff) { return 0; } // forward(); return 0x80; } boolean lastSync = false; private int readByte() { if (sectorPos == -1) return 0; int data = gcrSector[sectorPos]; if (DEBUG_GCR) { System.out.println("C1541: Read byte from: " + sectorPos + " => " + Integer.toString(data, 16) + " " + i2c(data) + " " + cpu.cycles + " pc = " + Integer.toString(cpu.pc, 16)); } // forward(); return data; } // Roatet head forward to read next byte! private void forward() { if (diskModeWrite && (via2CA == 0xff) && currentByte != -1 && sectorPos >= 0) { bytesWritten++; if (DEBUG) { System.out.println("#### Write byte: " + Integer.toString(currentByte, 16) + " (" + Integer.toString(gcrSector[sectorPos], 16) + ")" + (char) currentByte + " y=" + cpu.y + " written: " + bytesWritten + " SectorPos: " + sectorPos + " " + cpu.cycles); } // Should this be written a byte "backwards"?? gcrWriteSector[sectorPos] = currentByte; } sectorPos++; if (sectorPos == GCR_SECTOR_SIZE) { finishWrite(); sectorPos = -1; // Some extra cycles when switching sector?!! nextAutoforward += 1000; sector = (sector + 1) % currentTrackSize; readGCRSector(track, sector); } update(this, SECTOR_UPDATE); // IF not sync now or last time... if (diskModeWrite || sync() != 0 || !lastSync) { // Only trigger byte ready when not at sync bytes! cpu.triggerByteReady(); } lastSync = sync() == 0; } private void headOut() { if (hTrack > 2) { hTrack--; } else { headOutBeyond++; } System.out.println("1541: Move head In to: " + hTrack); updateHTrack(); update(this, HEAD_MOVED); } private void headIn() { if (hTrack < 70) hTrack++; System.out.println("1541: Move head Out to: " + hTrack); updateHTrack(); update(this, HEAD_MOVED); } private void updateHTrack() { if (track != hTrack >> 1) { // Takes some time before ready for next... nextAutoforward = cpu.cycles + 100000; finishWrite(); track = hTrack >> 1; sector = 0; // sectorPos = 0; sectorPos = -1; currentTrackSize = C64Reader.getSectorCount(track); System.out.println("1541: New Track " + track + " reading: " + track + ", " + sector + " s/t: " + currentTrackSize); readGCRSector(track, sector); } } // returns a 10 bit GCR from a 8 bit byte private static long getGCR(int b) { return (GCR[b >> 4] << 5) | GCR[b & 15]; } private static char i2c(int i) { if (i < 32) return '.'; return (char) i; } public static int makeGCR(int[] gcrBuf, int pos, int b1, int b2, int b3, int b4) { int cSum = b1 ^ b2 ^ b3 ^ b4; if (DEBUG_GCR) { System.out.print("GCR 4 => 5: " + i2c(b1) + i2c(b2) + i2c(b3) + i2c(b4) + ", " + b1 + ", " + b2 + ", " + b3 + ", " + b4); } long gcr = (getGCR(b1) << 30) | (getGCR(b2) << 20) | (getGCR(b3) << 10) | getGCR(b4); long bits = 32; for (int i = 0, n = 5; i < n; i++) { gcrBuf[pos++] = (int) ((gcr >> bits) & 0xff); bits = bits - 8; if (DEBUG_GCR) { if (i == 0) System.out.print(" => "); else System.out.print(", "); System.out.print(gcrBuf[pos - 1]); } } if (DEBUG_GCR) { System.out.println(""); } return cSum; } // Converts on the fly - should probably cache? private void readGCRSector(int track, int sector) { currentTrack = track; currentSector = sector; if (gcrCacheSector[track][sector] != null) { if (DEBUG_GCR) log("Using GCR Sector cache for " + track + "," + sector); gcrSector = gcrCacheSector[track][sector]; gcrWriteSector = gcrSector; return; } if (DEBUG_GCR) log("Reading and GCR:ing sector: " + track + ", " + sector); byte[] sectorBuf = reader.getSector(track, sector); int pos = 0; int cSum = 0; gcrSector = new int[GCR_SECTOR_SIZE]; // First data is sync! gcrSector[pos++] = 0xff; makeGCR(gcrSector, pos, 0x08, sector ^ track ^ diskID1 ^ diskID2, sector, track); pos += 5; makeGCR(gcrSector, pos, diskID2, diskID1, 0x0f, 0x0f); pos += 5; // pos = 11 for (int i = 0; i < 9; i++) { gcrSector[pos++] = 0x55; } // Another sync - at position 20 (?) gcrSector[pos++] = 0xff; // pos = 21 // cancel out first 7 by setting cSum to 7 cSum = 0x07; cSum ^= makeGCR(gcrSector, pos, 0x07, sectorBuf[0] & 0xff, sectorBuf[1] & 0xff, sectorBuf[2] & 0xff); pos += 5; // pos = 26 for (int i = 3, n = 255; i < n; i += 4) { cSum ^= makeGCR(gcrSector, pos, sectorBuf[i] & 0xff, sectorBuf[i + 1] & 0xff, sectorBuf[i + 2] & 0xff, sectorBuf[i + 3] & 0xff); pos += 5; } cSum ^= sectorBuf[255] & 0xff; // Verify checksum // int s2 = 0; // for (int i = 0, n = 256; i < n; i++) { // s2 ^= (sectorBuf[i] & 0xff); // } // System.out.println("### cSum: " + cSum + " = " + s2); makeGCR(gcrSector, pos, sectorBuf[255] & 0xff, cSum & 0xff, 0, 0); pos += 5; for (int i = 0, n = 8; i < n; i++) { gcrSector[pos++] = 0x55; } // Create a similar write sector!?!?? //System.arraycopy(gcrSector,0, gcrWriteSector, 0, gcrWriteSector.length); // System.out.println("Converted to: " + pos + " bytes"); // Allow writing the sector!!! gcrWriteSector = gcrSector; gcrCacheSector[track][sector] = gcrSector; } private void convertGCRSector() { int[] buffer = new int[256 + 10]; int pos = 0; int start = 0; for (int i = 5, n = 30; i < n; i++) { if (gcrSector[i] == 0xff) { start = i + 1; } } for (int i = 0, n = 325; i < n; i += 5) { convertFromGCR(buffer, pos, i + start); pos += 4; } System.out.println("Converted " + pos + " bytes"); // First byte is a 7, then last 3 is checksum + 2 zeros => 256 bytes byte[] newSector = new byte[256]; for (int i = 0, n = 255; i < n; i++) { newSector[i] = (byte) (buffer[i + 1] & 0xff); } reader.setSector(track, sector, newSector); } private void convertFromGCR(int[] buff, int pos, int posW) { long data = convertFromGCR(gcrWriteSector[posW], gcrWriteSector[posW + 1], gcrWriteSector[posW + 2], gcrWriteSector[posW + 3], gcrWriteSector[posW + 4]); // bits 19.15 + 14.10 9.5 + 4.0 System.out.println("Converting (" + posW + "): " + Long.toString(data, 16)); buff[pos++] = ((int)(data >> 24)) & 0xff; buff[pos++] = ((int)(data >> 16)) & 0xff; buff[pos++] = ((int)(data >> 8)) & 0xff; buff[pos++] = ((int)(data)) & 0xff; for (int i = 0, n = 4; i < n; i++) { int c = buff[pos - 4 + i]; System.out.println("Converted: " + c + " => " + (char) c); } } public static long convertFromGCR(long b1, long b2, long b3, long b4, long b5) { long data = (b1 << 32) + (b2 << 24) + (b3 << 16) + (b4 << 8)+ b5; System.out.println("Converting from: " + Long.toString(data, 16)); long d1 = GCR_REV[((int) (data >> 35)) & 0x1f] << 4 | GCR_REV[((int)(data >> 30)) & 0x1f]; long d2 = GCR_REV[((int)(data >> 25)) & 0x1f] << 4 | GCR_REV[((int)(data >> 20)) & 0x1f]; long d3 = GCR_REV[((int)(data >> 15)) & 0x1f] << 4 | GCR_REV[((int)(data >> 10)) & 0x1f]; long d4 = GCR_REV[((int)(data >> 5)) & 0x1f] << 4 | GCR_REV[((int)data) & 0x1f]; return (d1 << 24) + (d2 << 16) + (d3 << 8) + d4; } public static void main(String[] args) { String text = args[0]; int[] gcrEncoded = new int[text.length() * 2]; int gcrPos = 0; int gcrVal = 0; int gcrBits = 0; // GCR Encode for (int i = 0, n = text.length(); i < n; i++) { int c = text.charAt(i) & 0xff; gcrVal |= ((GCR[c >> 4] << 5 ) | GCR[c & 0xf]) << gcrBits; gcrBits += 10; // Output the GCR encoded data while (gcrBits >= 8) { gcrEncoded[gcrPos++] = gcrVal & 0xff; gcrBits -= 8; gcrVal = gcrVal >> 8; } } System.out.println("GCR Data:"); for (int i = 0, n = gcrPos; i < n; i++) { if (i % 16 == 0) { System.out.println(""); } System.out.print(Integer.toHexString(gcrEncoded[i]) + " "); } // Decode gcrVal = 0; gcrBits = 0; // Just printout... System.out.println("Decoded: " ); for (int i = 0, n = gcrPos; i < n; i++) { gcrVal |= gcrEncoded[i] << gcrBits; gcrBits += 8; if (gcrBits >= 10) { int val = gcrVal & 0x3ff; int bval = (GCR_REV[val >> 5] << 4) | (GCR_REV[val & 0x1f]); gcrVal = gcrVal >> 10; gcrBits -= 10; System.out.print((char) bval); } } System.out.println(""); // GCR Decode } public void stop() { } public void log(String text) { System.out.println("C1541: " + text); } }