using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace floppy { class Program { //the following are Saleae Logic captures exported to csv: static string dfn = "d:\\downloads\\floppy\\cops_tester_pds_master.csv"; //static string dfn = "d:\\downloads\\floppy\\cops_tester_test_disk.csv"; //static string dfn = "d:\\downloads\\floppy\\cops_tester_line_card.csv"; //corrupt //static string dfn = "d:\\downloads\\floppy\\cops_tester_romchk.csv"; //sector data uses this CRC static ushort Crc16Ccitt(byte[] bytes) { const ushort poly = 0x1021; ushort initialValue = 0xffff; ushort[] table = new ushort[256]; ushort temp, a; ushort crc = initialValue; for (int i = 0; i < table.Length; ++i) { temp = 0; a = (ushort)(i << 8); for (int j = 0; j < 8; ++j) { if (((temp ^ a) & 0x8000) != 0) temp = (ushort)((temp << 1) ^ poly); else temp <<= 1; a <<= 1; } table[i] = temp; } for (int i = 0; i < bytes.Length; ++i) { crc = (ushort)((crc << 8) ^ table[((crc >> 8) ^ (0xff & bytes[i]))]); } return crc; } //read a word from the bitstream at the given bit offset static int readword(byte[] bits, int bitoffset) { int wordread = 0; for (int i = 0; i < 16; i++) { wordread *= 2; wordread += bits[i + bitoffset]; } return wordread; } //read a byte from the bitstream at the given bit offset static byte readbyte(byte[] bits, int bitoffset) { byte byteread = 0; for (int i = 0; i < 8; i++) { byteread *= 2; byteread += bits[i + bitoffset]; } return byteread; } static byte[] nextsectortable = new byte[308]; //increment the sector number and locate next extent if necessary static int getnextsector(int cursector) { if (cursector % 4 == 3) //new extent, look it up in Next Sector Table return nextsectortable[cursector / 4]; else //same extent, return next sector return cursector + 1; } //absolute sector numbers are track*8+sector# static int gettrack(int abssectornum) { return abssectornum / 8; } static int getsector(int abssectornum) { return abssectornum % 8; } static void Main(string[] args) { int lines = 0; int ipulses = 0; int steps = 0; int rds = 0; string line; int curbit = 0; byte[] bits = new byte[100000]; byte[] bytes = new byte[12500]; int cside = 0; int filenum = 0; byte[,][] sectors = new byte[77, 8][]; StreamWriter log = new StreamWriter("d:\\downloads\\floppy\\dump\\floppy.log"); DateTime dt = DateTime.Now; System.IO.StreamReader file = new System.IO.StreamReader(dfn); int cpulses = 0; int ctrack = 0; int oldh = 0; bool lastbitdata = false; Int64 osamp = 0; int[] pulsewidths = new int[256]; for (int i = 0; i < 256; i++) { pulsewidths[i] = 0; } line = file.ReadLine(); //CSV header line = file.ReadLine(); //initial value while ((line = file.ReadLine()) != null) { string ls = line.Substring(0, line.IndexOf(',')); Int64 samp = 0; try { samp = Convert.ToInt64(ls); } catch (Exception x) { Console.WriteLine(x.ToString()); System.Diagnostics.Debugger.Break(); } string s = line.Substring(line.Length - 1, 1); int h = 0; try { h = Convert.ToInt32(s, 16); } catch (Exception x) { Console.WriteLine(x.ToString()); System.Diagnostics.Debugger.Break(); } lines++; if (((oldh & 1) != 0) && ((h & 1) == 0)) //index pulse { ipulses++; cpulses++; } if (((oldh & 2) != 0) && ((h & 2) == 0)) //step to next track - write current track bits to file { if (filenum < 77 && curbit > 16) { steps++; cpulses = 0; if (filenum < 77) cside = 0; else cside = 1; log.WriteLine(string.Format("{0} bits in track {1} side {2}", curbit, ctrack, cside)); for (int i = 0; i < bytes.Length; i++) bytes[i] = 0; int numbytes = (curbit + 7) / 8; int offset = 0; for (int i = 0; i < numbytes; i++) { for (int j = 0; j < 8; j++) { bytes[i] *= 2; bytes[i] += bits[i * 8 + j + offset]; } } for (int i = 0; i < curbit % 8; i++) bytes[numbytes] *= 2; string fname = string.Format("d:\\downloads\\floppy\\dump\\diskbits_s{0}_t{1}.bin", cside, ctrack); BinaryWriter writer = new BinaryWriter(File.Open(fname, FileMode.Create)); writer.Write(bytes, 0, numbytes); writer.Close(); int totpulses = 0; int validtimes = 0; for (int i = 0; i < 256; i++) { totpulses += pulsewidths[i]; if ((i >= 45 && i <= 55) || (i >= 90 && i <= 110)) validtimes += pulsewidths[i]; //Console.Write("S{0,1}T{1,2} {2,3}: {3,4} ", cside, ctrack, i, pulsewidths[i]); log.Write("{0,3}: {1,4} ", i, pulsewidths[i]); if ((i + 1) % 8 == 0) log.WriteLine(""); pulsewidths[i] = 0; } log.WriteLine("{0}% valid bit times", 100 * validtimes / totpulses); //Console.ReadKey(); //read sector header //scan bits for disk sync word AAAA int b = 0; for (int sec = 0; sec < 8; sec++) { while ((b < curbit) && (readword(bits, b) != 0xaaaa)) b++; b += 16; if (b < curbit) { int dswt = readword(bits, b); b += 16; int dsws1 = readword(bits, b); b += 16; int dsws2 = readword(bits, b); b += 16; log.WriteLine("sector header {0} {1} {2}", dswt, dsws1, dsws2); if (dswt != ctrack) log.WriteLine("error: sector header track {0} expected, but {1} read", ctrack, dswt); if (dsws1 != ctrack * 8 + sec) log.WriteLine("error: sector header sector {0} expected, but {1} read", ctrack * 8 + sec, dsws1); if (dsws2 != ctrack * 9 + sec) log.WriteLine("error: sector header checksum {0} expected, but {1} read", ctrack * 9 + sec, dsws2); } else log.WriteLine("error: sector header disk sync word AAAA not found"); sectors[ctrack, sec] = new byte[512]; //now read sector data //scan bits for disk sync word AAAA while ((b < curbit) && (readword(bits, b) != 0xaaaa)) b++; b += 16; if (b < curbit) { //read 512 bytes of sector data, verify CRC against next word for (int i = 0; i < 512; i++) { sectors[ctrack, sec][i] = readbyte(bits, b); b += 8; } Int64 crc = readword(bits, b); Int64 calccrc = Crc16Ccitt(sectors[ctrack, sec]); if (crc != calccrc) log.WriteLine("error: sector data crc {0:X4} expected, but {1:X4} read", calccrc, crc); else log.WriteLine("sector data crcs match: {0:X4}", calccrc); } else log.WriteLine("error: sector data disk sync word AAAA not found"); } filenum++; ctrack++; if (ctrack == 77) ctrack = 0; log.WriteLine(""); } curbit = 0; } if (((oldh & 4) != 0) && ((h & 4) == 0)) { //data or clock bit if (cside == 0 && cpulses == 2) //1 second = 6 revs; just look at 2nd read of track (probably first full read) { //histogram of pulse periods if (samp - osamp < 255) pulsewidths[samp - osamp]++; else pulsewidths[0]++; //are you a 1 or a 0? - time between pulses is nominally 50 samples or 100 samples, so use 75 to distinguish 1s from 0s if (samp - osamp > 75) //lots of time between pulses means data bit was 0 and both this pulse and last pulse were clock bits { lastbitdata = false; bits[curbit++] = 0; } else { //smaller time between pulses means data bit was 1; either this pulse or last pulse was a clock bit and the other was the data bit if (!lastbitdata) bits[curbit++] = 1; lastbitdata = !lastbitdata; } osamp = samp; rds++; } } oldh = h; if ((lines % 1000000) == 0) Console.Write("."); } file.Close(); //write the disk sectors to a file BinaryWriter dwriter = new BinaryWriter(File.Open("d:\\downloads\\floppy\\dump\\disk.bin", FileMode.Create)); for (int t = 0; t < 77; t++) for (int s = 0; s < 8; s++) dwriter.Write(sectors[t, s], 0, 512); dwriter.Close(); log.WriteLine(""); //parse the disk structure byte[] dvn1 = new byte[8]; for (int i = 0; i < 8; i++) dvn1[i] = sectors[0, 0][i + 155 * 2]; log.WriteLine("Disk Volume Name 1: {0}", System.Text.Encoding.UTF8.GetString(dvn1).TrimEnd('\0')); byte[] dhs = new byte[40]; for (int i = 0; i < 40; i++) { dhs[i] = sectors[0, 0][i + 159 * 2]; } log.WriteLine("Disk Header String: {0}", System.Text.Encoding.UTF8.GetString(dhs).TrimEnd('\0')); int fdsn = sectors[0, 0][154 * 2] * 256 + sectors[0, 0][154 * 2 + 1]; log.WriteLine("First directory sector number: {0:X4}", fdsn); byte[] dvn2 = new byte[8]; for (int i = 0; i < 8; i++) dvn2[i] = sectors[fdsn / 8, fdsn % 8][1 * 2 + i]; log.WriteLine("Disk Volume Name 2 : {0}", System.Text.Encoding.UTF8.GetString(dvn2).TrimEnd('\0')); int fbs = sectors[fdsn / 8, fdsn % 8][6 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][6 * 2 + 1]; log.WriteLine("First bad sector: {0:X4}", fbs); int lbs = sectors[fdsn / 8, fdsn % 8][7 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][7 * 2 + 1]; log.WriteLine("Last bad sector: {0:X4}", lbs); int bsc = sectors[fdsn / 8, fdsn % 8][8 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][8 * 2 + 1]; log.WriteLine("Bad sector count: {0}", bsc); int nas = sectors[fdsn / 8, fdsn % 8][16 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][16 * 2 + 1]; log.WriteLine("Next available sector: {0:X4}", nas); int las = sectors[fdsn / 8, fdsn % 8][17 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][17 * 2 + 1]; log.WriteLine("Last available sector: {0:X4}", las); int asc = sectors[fdsn / 8, fdsn % 8][18 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][18 * 2 + 1]; log.WriteLine("Available sector count: {0}", asc); int dss = sectors[fdsn / 8, fdsn % 8][26 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][26 * 2 + 1]; log.WriteLine("Directory starting sector: {0:X4}", dss); int ndn = dss; while (ndn != 0xffff) { ndn = sectors[0, 0][ndn / 2] * 256 + sectors[0, 0][ndn / 2 + 1]; log.WriteLine("Next directory sector number: {0:X4}", ndn); } int des = sectors[fdsn / 8, fdsn % 8][27 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][27 * 2 + 1]; log.WriteLine("Directory ending sector: {0:X4}", des); int ds = sectors[fdsn / 8, fdsn % 8][28 * 2] * 256 + sectors[fdsn / 8, fdsn % 8][28 * 2 + 1]; log.WriteLine("Directory size (sectors): {0}", ds); for (int i = 0; i < 308; i++) { nextsectortable[i] = sectors[0, 0][i]; } //files int cdsn = fdsn; int feo = 30; //file entries start here for first directory sector while ((sectors[cdsn / 8, cdsn % 8][feo * 2] * 256 + sectors[cdsn / 8, cdsn % 8][feo * 2 + 1] != 0xffff)) { log.WriteLine(""); byte[] fnb = new byte[9]; fnb[8] = 0; for (int i = 0; i < 8; i++) fnb[i] = sectors[cdsn / 8, cdsn % 8][feo * 2 + i]; string fn = System.Text.Encoding.UTF8.GetString(fnb).TrimEnd('\0'); log.WriteLine("File name : {0}", fn); byte[] fmb = new byte[4]; fmb[3] = 0; for (int i = 0; i < 3; i++) fmb[i] = sectors[cdsn / 8, cdsn % 8][(feo + 4) * 2 + i]; string fm = System.Text.Encoding.UTF8.GetString(fmb).TrimEnd('\0'); log.WriteLine("File modifier : {0}", fm); int it = sectors[cdsn / 8, cdsn % 8][(feo + 5) * 2 + 1]; string its; if (it == 0) its = "Universal"; else if (it == 1) its = "Load Module"; else if (it == 2) its = "Main Program"; else if (it == 3) its = "Overlay"; else if (it == 4) its = "Block"; else if (it == 5) its = "Symbolic"; else if (it == 6) its = "System"; else if (it == 7) its = "Data"; else its = "Unknown"; log.WriteLine("Internal type : {0}", its); int ssn = sectors[cdsn / 8, cdsn % 8][(feo + 6) * 2] * 256 + sectors[cdsn / 8, cdsn % 8][(feo + 6) * 2 + 1]; log.WriteLine("Starting sector number : {0:X4} (${1:X5})", ssn, ssn * 512); int tsu = (sectors[cdsn / 8, cdsn % 8][(feo + 8) * 2] & 0x03) * 256 + sectors[cdsn / 8, cdsn % 8][(feo + 8) * 2 + 1]; int esn = sectors[cdsn / 8, cdsn % 8][(feo + 7) * 2] * 256 + sectors[cdsn / 8, cdsn % 8][(feo + 7) * 2 + 1]; byte[] fd = new byte[tsu * 512]; int fo = 0; int nsn = ssn; while (nsn != 0xffff) { for (int s = 0; s < 4; s++) for (int b = 0; b < 512; b++) fd[fo++] = sectors[gettrack(nsn + s), getsector(nsn + s)][b]; nsn = sectors[0, 0][nsn / 2] * 256 + sectors[0, 0][nsn / 2 + 1]; if (nsn != 0xffff) log.WriteLine("Next sector number: {0:X4} (${1:X5})", nsn, nsn * 512); else log.WriteLine("Next sector number: {0:X4}", nsn); } //truncate file at EOF //no info on this, so I'm guessing: //look for EOF word in ending sector, starting from end //if not found, assume at end of ending sector int lastusedsectorofextent = esn%4; fo = -1; for (int i = (tsu - (3 - lastusedsectorofextent)) * 512 - 1; i >= (tsu - (3 - lastusedsectorofextent)) * 512 - 513; i--) { if (fd[i] == 0 && fd[i + 1] == 0x1c) //EOF { fo = i; break; } } if (fo == -1) fo = (tsu - (3 - lastusedsectorofextent)) * 512; dwriter = new BinaryWriter(File.Open(string.Format("d:\\downloads\\floppy\\dump\\{0}.{1}", fn, fm), FileMode.Create)); dwriter.Write(fd, 0, fo); dwriter.Close(); log.WriteLine("Ending sector number : {0:X4} (${1:X5})", esn, esn * 512); int df = (sectors[cdsn / 8, cdsn % 8][(feo + 8) * 2] & 0x80) >> 7; log.WriteLine("Delete flag : {0}", df); int pl = (sectors[cdsn / 8, cdsn % 8][(feo + 8) * 2] & 0x78) >> 3; log.WriteLine("Protect level : {0}", pl); log.WriteLine("Total sectors used : {0}", tsu); int fvn = sectors[cdsn / 8, cdsn % 8][(feo + 9) * 2] * 256 + sectors[cdsn / 8, cdsn % 8][(feo + 9) * 2 + 1]; log.WriteLine("File version number : {0}", fvn); feo += 10; //10 words/file entry if (feo > 247) { //next directory sector cdsn = getnextsector(cdsn); feo = 0; //file entries start here for subsequent directory sectors } } log.WriteLine(""); log.WriteLine(string.Format("duration: {0}", DateTime.Now - dt)); log.WriteLine(string.Format("{0} lines, {1} index pulses, {2} steps, {3} rds", lines, ipulses, steps, rds)); log.Close(); } } }