// Example input (from DOS prompt) // ExtractPX0.exe "D:\Applications\APPRO6.2.27.167\OrigPickFiles\35.datafiles1.px0" static void Main(string[] args) { string inPath = (null == args || 1 != args.Length || null == args[0]) ? null : args[0].Trim(); if (string.IsNullOrEmpty(inPath)) { // We were expecting only a single parameter.. ShowUsage(); return; } var fi = new FileInfo(inPath); if (!fi.Exists) { // Parameter does not represent a file Console.WriteLine("File not found! '{0}'", inPath); return; } if (0 != string.Compare(fi.Extension, ".px0", true)) { // Input files are expected to have a '.px0' extension! Console.WriteLine("Not a '.px0' file! '{0}'", inPath); return; } string outPath = Path.ChangeExtension(fi.FullName, ".img"); if (File.Exists(outPath)) { // Do not overwrite existing '.img' files! Console.WriteLine("Output file already exists! '{0}'", outPath); return; } var sectorDataList = new List <SectorData>(); try { // Get PXO file bytes. If compressed data existed, these bytes will // represent the decompressed form! var px0Bytes = GetPX0ImageBytes(inPath); // Init a pointer we will use to traverse the PX0 file byte array var px0Ptr = 0; // Check that bytes represent valid data, read file header structure. var px0Hdr = PX0Header.GetHeader(px0Bytes, ref px0Ptr); px0Ptr++; // On first byte *after* header PX0CommentHeader px0CmtHdr = null; if ((px0Hdr.Stepping & 0x80) != 0) { // Extract 'comment header' px0CmtHdr = PX0CommentHeader.GetCommentHeader(px0Bytes, ref px0Ptr); px0Ptr++; // On first byte *after* 'comment header' } // Loop over track headers PX0TrackHeader trkHdr; while ((trkHdr = PX0TrackHeader.GetTrackHeader(px0Bytes, ref px0Ptr)) != null) { if (trkHdr.NumberOfSectors == 255) { break; // EOF marker! } px0Ptr++; // On first byte *after* 'track header' for (int sNo = 0; sNo < trkHdr.NumberOfSectors; sNo++) { var sectHeader = PX0SectorHeader.GetSectorHeader(px0Bytes, ref px0Ptr); /* * Calculate 'Logical Sector' number (from: http://stackoverflow.com/questions/5774164/lba-and-cluster or * http://en.wikipedia.org/wiki/Cylinder-head-sector) * There are many sector numbering schemes on disk drives. One of the earliest was CHS (Cylinder-Head-Sector). * One sector can be selected by specifying the cylinder (track), read/write head and sector per track triplet. * This numbering scheme depends on the actual physical characteristics of the disk drive. * The first logical sector resides on cylinder 0, head 0, sector 1. The second is on sector 2, and so on. * If there isn't any more sectors on the disk (eg. on a 1.44M floppy disk there's 18 sectors per track), * then the next head is applied, starting on sector 1 again, and so on. * * You can convert CHS addresses to an absolute (or logical) sector number with a little math: * * LSN = (C * Nh + H) * Ns + S - 1 * * where C, H and S are the cylinder, head and sector numbers according to CHS adressing, while Nh and Ns are * the number of heads and number of sectors per track (cylinder), respectively. * * To convert a logical sector number into a cylinder, head and sector number: * S = (LSN mod Ns) + 1 * H = (LSN / Ns) mod Nh * C = LSN / (Ns * Nh) */ var LSN = (trkHdr.CylNumber * px0Hdr.Sides + trkHdr.SideHeadNumber) * trkHdr.NumberOfSectors + sectHeader.SectorNumber - 1; //System.Diagnostics.Debug.WriteLine(string.Format("LSN = {0} where: C: {1}, Nh: {2}, H: {3}, Ns: {4}, S: {5}", // LSN, trkHdr.CylNumber, px0Hdr.Sides, trkHdr.SideHeadNumber, trkHdr.NumberOfCylinders, sectHeader.SectorNumber)); sectorDataList.Add(new SectorData(LSN, sectHeader.sDta)); px0Ptr++; // On first byte *after* 'sector header' } } if (sectorDataList.Count < 1) { throw new Exception("No sector data extracted!"); } // Sort sector data by 'logical sector number' sectorDataList.Sort(); } catch (Exception ex) { Console.WriteLine("*ERROR* - Failed to convert file '{0}' - error: '{1}'", inPath, ex.Message); return; } // Write out the image file! try { using (var binWriter = new BinaryWriter(File.Open(outPath, FileMode.Create))) { // Write out the disk image!! foreach (var td in sectorDataList) { binWriter.Write(td.dta); } binWriter.Flush(); } Console.WriteLine("Finished writing image file: " + outPath); } catch (Exception ex) { Console.WriteLine("*ERROR* - Failed to write image file '{0}' - error: '{1}'", outPath, ex.Message); } }
public static PX0SectorHeader GetSectorHeader(byte[] dta, ref int pos) { if (pos >= dta.Length) { throw new Exception("Sector header data missing?"); } var hdr = new PX0SectorHeader(dta[pos], dta[pos + 1], dta[pos + 2], dta[pos + 3], dta[pos + 4], dta[pos + 5]); pos += 5; // On last byte of track header if ((hdr.Flags & SEC_DOS) == 0 && (hdr.Flags & SEC_NODAT) == 0) { var dtaBlkSize = (ushort)(dta[pos + 2] << 8 | dta[pos + 1]); byte compMethod = dta[pos + 3]; pos += 3; // On last byte of sector data header // Note that we are expanding 'dtaBlkSize-1' bytes into 'hdr.sDta.Length' bytes! var dptr = 0; while (dptr < hdr.sDta.Length) { switch (compMethod) { case 0: // Raw sector data for (var j = 0; j < hdr.sDta.Length; j++) { hdr.sDta[dptr++] = dta[++pos]; } break; case 1: // Repeated 2-byte pattern var cnt = (ushort)(dta[pos + 2] << 8 | dta[pos + 1]); var b1 = dta[pos + 3]; var b2 = dta[pos + 4]; pos += 4; // On last byte of pattern defn field while (cnt > 0) { cnt--; hdr.sDta[dptr++] = b1; hdr.sDta[dptr++] = b2; } break; case 2: // RLE block // Each entry begins with a 1 byte length value or 0. // // If 0 then this entry is for a literal block. The next byte // indicates a length 'lgth', and the following 'lgth' bytes are copied // into the sector data as raw bytes (similar to Encoding // method==0 except for only a portion of the sector). // // If not 0, then the length 'lgth' is determined as the length value ** 2 // The next byte indicates a repeat count 'kount'. A block of 'lgth' bytes // is then read once from the file, and repeated in the sector data // 'kount' times. var lgth = dta[++pos]; // 'pos' left on length byte if (0 == lgth) { // Literal data block lgth = dta[++pos]; // 'pos' left on length byte while (lgth > 0) { lgth--; hdr.sDta[dptr++] = dta[++pos]; } } else { // Repeated fragment lgth = (byte)(1 << lgth); // Length byte kount = dta[++pos]; // Count ('pos' left on count byte) var frag = new byte[lgth]; for (var j = 0; j < lgth; j++) { frag[j] = dta[++pos]; } while (kount > 0) { kount--; for (var j = 0; j < lgth; j++) { hdr.sDta[dptr++] = frag[j]; } } } break; default: throw new Exception("Invalid data mode in sector!"); } } var crc = (byte)(GetCRC(hdr.sDta, 0, hdr.sDta.Length, 0) & 0xff); if (crc != hdr.CRC) { throw new Exception("Sector data CRC is invalid!"); } } return(hdr); }