public static PX0CommentHeader GetCommentHeader(byte[] dta, ref int pos) { var dlen = (ushort)(dta[pos + 3] << 8 | dta[pos + 2]); var cBytes = new byte[dlen]; for (var i = 0; i < dlen; i++) { byte b = dta[pos + 10 + i]; if (b == 0) { b = (byte)'\n'; } cBytes[i] = b; } var hdr = new PX0CommentHeader( (ushort)(dta[pos + 1] << 8 | dta[pos]), dlen, dta[pos + 4], dta[pos + 5], dta[pos + 6], dta[pos + 7], dta[pos + 8], dta[pos + 9], Encoding.ASCII.GetString(cBytes, 0, dlen) ); var crc = GetCRC(dta, pos + 2, 8 + dlen, 0); if (crc != hdr.CRC) { throw new Exception("Comment Header CRC is invalid!"); } pos += (dlen + 9); // On last byte of comment header return(hdr); }
// 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); } }