/* * Copyright (c) 2007 Sean C. Rhea (srhea@srhea.net), * Justin F. Knotzke (jknotzke@shampoo.ca) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // GENERAL NOTES: // You will see a lot of 'commented out' code in the parsing functions. This is // because the decoding is skipped over, but may be useful in the future and // it helps to reference back to the file format spec. For example a whole // block of decoding of numbers and texts is replaced with p+= 64 in one part of // the code and that is not particularly enlightening. // // The code is refactored from my wko2csv sourceforge project // // Whilst I have applied the coding standards in style.txt to the letter I suspect // that some of the casting is as a result of poor design and could be removed // altogether. Naybe when I have more time. // ISSUES // // 1. WKO_HOMEDIR EXTERNAL GLOBAL // The homedirectory is needed to create rideNotes, but is not available either // as a global or a passed variable -- since the RideFile function is pure virtual // I was unable to modify the caller to pass it, so it is stored in an external // global variable called WKO_HOMEDIR. // // 2. UNUSED GRAPHS // Windspeed, Temperature, Slope et al are available from WKO // data files but is discarded currently since it is not supported by RideFile // // 3. GOTO // During the ParseHeaderData there are a couple of nasty goto statements // to breakout of nested loops / if statements. // // 4. WKO_device and WKO_GRAPHS STATIC GLOBALS // Shared between a number of functions to simplify parameter passing and avoid // refactoring as a class. // #include "WkoRideFile.h" #include #include #include #include #include // for std::sort #include #include "math.h" // local holding varables shared between WkoParseHeaderData() and WkoParseRawData() static WKO_ULONG WKO_device; // Device ID used for this workout static char WKO_GRAPHS[32]; // GRAPHS available in this workout static QList references; // saved with data point references static int wkoFileReaderRegistered = RideFileFactory::instance().registerReader( "wko", "WKO+ Files", new WkoFileReader()); //****************************************************************************** // Main Entry Point from MainWindow() called to read data file //****************************************************************************** RideFile *WkoFileReader::openRideFile(QFile &file, QStringList &errors) const { WKO_UCHAR *headerdata, *rawdata, *footerdata; WKO_ULONG version; QFileInfo fileinfo(file); if (!file.open(QFile::ReadOnly)) { errors << ("Could not open ride file: \"" + file.fileName() + "\""); return NULL; } // read in the whole file and pass to parser routines for decoding boost::scoped_array entirefile(new WKO_UCHAR[file.size()]); // with a nod to c++ neophytes // read entire raw data stream QDataStream *rawstream(new QDataStream(&file)); headerdata = &entirefile[0]; rawstream->readRawData(reinterpret_cast(headerdata), file.size()); file.close(); // check it is version 28 of the format (the only version supported right now) donumber(headerdata+4, &version); if (version < 28) { errors << ("Version of file is too old, open and save in WKO then retry: \"" + file.fileName() + "\""); return NULL; } else if (version >29) { errors << ("Version of file is new and not fully supported yet: \"" + file.fileName() + "\""); } // Golden Cheetah ride file RideFile *rideFile = new RideFile; // read header data and store details into rideFile structure rawdata = WkoParseHeaderData(fileinfo.fileName(), headerdata, rideFile, errors); // Parse raw data (which calls rideFile->appendPoint() with each sample if (rawdata) footerdata = WkoParseRawData(rawdata, rideFile, errors); else return NULL; // Post process the ride intervals to convert from point // offsets to time in seconds QVector datapoints = rideFile->dataPoints(); for (int i=0; iname; if (references.at(i)->start < datapoints.count()) add.start = datapoints.at(references.at(i)->start)->secs; else continue; // out of bounds if (references.at(i)->stop < datapoints.count()) add.stop = datapoints.at(references.at(i)->stop)->secs + rideFile->recIntSecs()-.001; else continue; // out of bounds rideFile->addInterval(add.start, add.stop, add.name); } // free up memory for (int i=0; isetRecIntSecs(interval); /*------------------------------------------------------------------------------ * RUN THROUGH EACH RAW DATA RECORD *==============================================================================*/ rdist=rtime=0; while (records) { unsigned int marker; WKO_LONG sval=0; WKO_ULONG val=0; unsigned long valp; // for printf long svalp; // for printf // reset point values; cad= hr= km= kph= nm= watts= 0.0; marker = get_bits(thelot, bit++, 1); if (marker) { // OK so we have a sample to collect, format and get ready to output records--; for (i=0; WKO_GRAPHS[i] != '\0'; i++) { val = get_bits(thelot, bit, WKO_graphbits[i]); /* move onto next value */ bit += WKO_graphbits[i]; if (WKO_GRAPHS[i] != 'D' && WKO_GRAPHS[i] != 'G' && val==WKO_nullval[i]) { // null value GRAPHDATA[i][0] = '\0'; } else { /* apply conversion etc */ switch (WKO_GRAPHS[i]) { case '+' : /* Temperature - signed */ if (get_bit(thelot, bit-1)) { /* is negative */ val ^= WKO_xormasks[WKO_graphbits[i]]; sval = val * -1; } else { sval = val; } svalp = sval; sprintf(GRAPHDATA[i], "%8ld", svalp); break; case '^' : /* Slope */ case 'W' : /* Wind speed */ case 'A' : /* Altitude */ if (get_bit(thelot, bit-1)) { /* is negative */ val ^= WKO_xormasks[WKO_graphbits[i]]; sval = val * -1; if (imperialflag && WKO_GRAPHS[i]=='A') val = long((double) val * MTOFT); if (imperialflag && WKO_GRAPHS[i]=='W') val = long((double) val * KMTOMI); svalp = sval; valp = val; sprintf(GRAPHDATA[i], "%6ld.%1ld", svalp/10, valp%10); alt = val; alt /= 10; } else { if (imperialflag && WKO_GRAPHS[i]=='A') val = long((double) val * MTOFT); if (imperialflag && WKO_GRAPHS[i]=='W') val = long((double) val * KMTOMI); valp=val; sprintf(GRAPHDATA[i], "%6ld.%1ld", valp/10, valp%10); alt = val; alt /=10; } break; case 'T' : /* Torque */ if (imperialflag && WKO_GRAPHS[i]=='S') val = long((double)val * KMTOMI); valp=val; sprintf(GRAPHDATA[i], "%6ld.%1ld", valp/10, valp%10); nm = val; nm /=10; break; case 'S' : /* Speed */ if (imperialflag && WKO_GRAPHS[i]=='S') val = long((double)val * KMTOMI); valp=val; sprintf(GRAPHDATA[i], "%6ld.%1ld", valp/10, valp%10); kph = val; kph/= 10; // distance is not available so we need to calculate it if (calculate_distance) { double distance; double f, xi, xf; // inc is in 1000ths of a second kph val is kph*10 rdist += (inc * kph) / 36; distance = rdist; distance /= 100000; // convert to imperial units ? if (imperialflag) distance *= KMTOMI; // round to max of 3 decimal places xf = modf(distance,&xi); f = floor(xf*1000+0.5)/1000.0; sprintf(GRAPHDATA[i], "%g", xi+f); km = xi+f; } break; case 'D' : /* Distance - running total to 3 decimal places */ double distance; double f, xi, xf; // increment BEFORE not AFTER rdist += val; // conversion may be required distance = rdist; distance /= 100000; if (imperialflag) distance *= KMTOMI; // to max of 3 decimal places xf = modf(distance,&xi); f = floor(xf*1000+0.5)/1000.0; sprintf(GRAPHDATA[i], "%g", xi+f); km = xi+f; break; case 'G' : /* two longs */ { signed long llat, llon; char slat[20], slon[20]; // stored 2s complement // conversion is * 180 / 2^31 val = get_bits(thelot, bit-64,32); memcpy(&llat, &val, 4); lat = (double)llat; lat *= 0.00000008381903171539306640625; sprintf(slat, "%-3.9g", lat); val = get_bits(thelot, bit-32,32); memcpy(&llon, &val, 4); lon = (double)llon; lon *= 0.00000008381903171539306640625; sprintf(slon, "%-3.9g", lon); sprintf(GRAPHDATA[i], "%13s %13s", slat,slon); } break; case 'P' : watts = val; valp=val; sprintf(GRAPHDATA[i], "%8lu", valp); // just output as numeric break; case 'H' : hr = val; valp=val; sprintf(GRAPHDATA[i], "%8lu", valp); // just output as numeric break; case 'C' : cad = val; valp = val; sprintf(GRAPHDATA[i], "%8lu", valp); // just output as numeric break; } } } // Lets check to see if it is a null record? // We ignores checking GPS data cause its too hard to do at this point // and rarely (if ever?) affects the outcome for (i=0, isnull=1; WKO_GRAPHS[i] != '\0'; i++) if (WKO_GRAPHS[i] != 'G' && WKO_GRAPHS[i] != 'D' && GRAPHDATA[i][0] != '\0') isnull=0; // Now output this sample if it is not a null record if (!isnull) { // !! needs to be modified to support the new alt patch rideFile->appendPoint((double)rtime/1000, cad, hr, km, kph, nm, watts, alt, lon, lat, 0.0, 0); } // increment time - even for null records (perhaps especially for null // records since they are there specifically to handle pause in recording! rtime += inc; } else { // pause record increments time /* set the increment */ unsigned long pausetime; int pausesize; if (WKO_device != 0x14) pausesize=42; else pausesize=39; pausetime = get_bits(thelot, bit, 32); /* set increment value -> if followed by a null record it is to show a pause in recording -- velotrons seem to cause lots and lots of these */ inc = pausetime; bit += pausesize; } } return thelot + (bit/8); } /********************************************************************** * ZIP THROUGH ALL THE WKO SPECIFIC DATA, LAND ON THE RAW DATA * AND RETURN A POINTER TO THE FIRST BYTE OF THE RAW DATA * * WkoParseHeadeData() - read through file and land on the raw data * *********************************************************************/ WKO_UCHAR *WkoParseHeaderData(QString fname, WKO_UCHAR *fb, RideFile *rideFile, QStringList &errors) { unsigned long julian, sincemidnight; WKO_UCHAR *goal, *notes, *code; // save location of WKO metadata enum configtype lastchart; unsigned int x,z,i; /* utility holders */ WKO_ULONG num; WKO_ULONG ul; WKO_ULONG sport; WKO_USHORT us; double g; boost::scoped_array txtbuf(new WKO_UCHAR[1024000]); /* working through filebuf */ WKO_UCHAR *p = fb; /*************************************************** * 0: FILE HEADER ***************************************************/ p += donumber(p, &ul); /* 1: magic */ p += donumber(p, &ul); /* 2: version */ /*************************************************** * 1: JOURNAL TAB ***************************************************/ p += donumber(p, &ul); /* 3: date days since 01/01/1901 */ julian = ul + 2415386; // 1/1/1901 is day 2415386 in julian days god bless google. goal = p; p += dotext(p, &txtbuf[0]); /* 4: goal */ notes = p; p += dotext(p, &txtbuf[0]); /* 5: notes */ p += dotext(p, &txtbuf[0]); /* 6: graphs */ strcpy(reinterpret_cast(WKO_GRAPHS), reinterpret_cast(&txtbuf[0])); // save those graphs away p += donumber(p, &sport); /* 7: sport */ code = p; p += dotext(p, &txtbuf[0]); /* 8: workout code */ p += donumber(p, &ul); /* 9: duration 000s of seconds */ p += dotext(p, &txtbuf[0]); /* 10: lastname */ p += dotext(p, &txtbuf[0]); /* 11: firstname */ p += donumber(p, &ul); /* 12: time of day */ sincemidnight = ul; char rideFileRegExp[] = "^((\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)" "_(\\d\\d)_(\\d\\d)_(\\d\\d))\\.(.+)$"; QRegExp rx(rideFileRegExp); if (rx.exactMatch(fname)) { // set the date and time from the filename QDate date(rx.cap(2).toInt(), rx.cap(3).toInt(),rx.cap(4).toInt()); QTime time(rx.cap(5).toInt(), rx.cap(6).toInt(),rx.cap(7).toInt()); QDateTime datetime(date, time); rideFile->setStartTime(datetime); } else { // date not available from filename use WKO metadata QDateTime datetime(QDate::fromJulianDay(julian), QTime(sincemidnight/360000, (sincemidnight%360000)/6000, (sincemidnight%6000)/100)); rideFile->setStartTime(datetime); } // Create a Notes file for Goal, Notes and Workout Code from original QChar zero = QLatin1Char('0'); QString notesFileName = QString("%1_%2_%3_%4_%5_%6.notes") .arg(rideFile->startTime().date().year(), 4, 10, zero) .arg(rideFile->startTime().date().month(), 2, 10, zero) .arg(rideFile->startTime().date().day(), 2, 10, zero) .arg(rideFile->startTime().time().hour(), 2, 10, zero) .arg(rideFile->startTime().time().minute(), 2, 10, zero) .arg(rideFile->startTime().time().second(), 2, 10, zero); // Create the Notes file ONLY IF IT DOES NOT ALREADY EXIST QFile notesFile(WKO_HOMEDIR + "/" + notesFileName); if (!notesFile.exists()) { notesFile.open(QFile::WriteOnly); QTextStream out(¬esFile); QString scode, sgoal, snote; // Sport type switch (sport) { case 0x01 : out << "Swim " ; break; case 0x02 : out << "Bike " ; break; case 0x03 : out << "Run " ; break; case 0x04 : out << "Brick " ; break; case 0x05 : out << "Cross Train " ; break; case 0x06 : out << "Race " ; break; case 0x07 : out << "Day Off " ; break; case 0x08 : out << "Mountain Bike " ; break; case 0x09 : out << "Strength " ; break; case 0x0B : out << "XC Ski " ; break; case 0x0C : out << "Rowing " ; break; default : case 0x64 : out << "Other" << endl; break; } // Workout Code dotext(code, &txtbuf[0]); scode = (const char *)&txtbuf[0]; out << scode << endl; dotext(goal, &txtbuf[0]); sgoal = (const char *)&txtbuf[0]; out << "WORKOUT GOAL" << endl << sgoal << endl; dotext(notes, &txtbuf[0]); snote = (const char *)&txtbuf[0]; out << endl << "WORKOUT NOTES" << endl << snote << endl; notesFile.close(); } p += donumber(p, &ul); /* 13: distance travelled in meters */ p += donumber(p, &ul); /* 14: device recording interval */ p += donumber(p, &ul); /* 15: athlete max heartrate */ p += donumber(p, &ul); /* 16: athlete threshold heart rate */ p += donumber(p, &ul); /* 17: athlete threshold power */ p += dodouble(p, &g); /* 18: athlete threshold pace */ p += donumber(p, &ul); /* 19: weight in grams/10 */ p += 28; //p += donumber(p, &ul); /* 20: unknown */ //p += donumber(p, &ul); /* 21: unknown */ //p += donumber(p, &ul); /* 22: unknown */ //p += donumber(p, &ul); /* 23: unknown */ //p += donumber(p, &ul); /* 24: unknown */ //p += donumber(p, &ul); /* 25: unknown */ //p += donumber(p, &ul); /* 26: unknown */ /*************************************************** * 2: GRAPH TAB - MAX OF 16 CHARTING GRAPHS ***************************************************/ p += donumber(p, &ul); /* 27: graph view */ p += donumber(p, &ul); /* 28: WKO_device type */ WKO_device = ul; // save WKO_device #if 0 switch (WKO_device) { case 0x01 : rideFile->setDeviceType("Powertap (via WKO)"); break; case 0x04 : rideFile->setDeviceType("SRM (via WKO)"); break; case 0x05 : rideFile->setDeviceType("Polar (via WKO)"); break; case 0x06 : rideFile->setDeviceType("Computrainer/Velotron (via WKO)"); break; case 0x11 : rideFile->setDeviceType("Ergomo (via WKO)"); break; case 0x12 : rideFile->setDeviceType("Garmin Edge 205/305 (via WKO)"); break; case 0x13 : rideFile->setDeviceType("Garmin Edge 705 (via WKO)"); break; case 0x14 : rideFile->setDeviceType("Ergomo (via WKO)"); break; case 0x16 : rideFile->setDeviceType("Cycleops 300PT (via WKO)"); break; case 0x19 : rideFile->setDeviceType("Ergomo (via WKO)"); break; default : rideFile->setDeviceType("Unknown Device (via WKO)"); break; } #else rideFile->setDeviceType("WKO"); #endif p += donumber(p, &ul); /* 29: unknown */ for (i=0; i< 16; i++) { // 16 types of chart data p += 44; //p += dofloat(p, &f) /* 30: unknown */ //p += dofloat(p, &f) /* 31: unknown */ //p += dofloat(p, &f) /* 32: unknown */ //p += dofloat(p, &f) /* 33: unknown */ //p += dofloat(p, &f) /* 34: unknown */ //p += dofloat(p, &f) /* 35: unknown */ //p += dofloat(p, &f) /* 36: unknown */ //p += donumber(p, &ul); /* 37: x-axis maximum */ //p += donumber(p, &ul); /* 38: show chart? */ //p += donumber(p, &ul); /* 39: autoscale? */ //p += donumber(p, &ul); /* 40: autogridlines? */ p += doshort(p, &us); /* 41: number of gridlines XXVARIABLEXX */ p += (us * 8); // 2 longs x number of gridlines } /* Ranges */ // The ranges are stored as references to data points // whilst the RideFileInterval structure uses start and stop // in seconds. We cannot translate from one to the other at this // point because the raw data has not been parsed yet. // So intervals are created here with point references // and they are post-processed after the call to // WkoParseRawData in openRideFile above. p += doshort(p, &us); /* 237: Number of ranges XXVARIABLEXX */ for (i=0; iname = reinterpret_cast(&txtbuf[0]); p += dotext(p, &txtbuf[0]); p += donumber(p, &ul); p += donumber(p, &ul); add->start = ul; p += donumber(p, &ul); add->stop = ul; p += 12; //p += donumber(p, &ul); //p += donumber(p, &ul); //p += donumber(p, &ul); // add to intervals - referencing data point for interval references.append(add); } /*************************************************** * 3: DEVICE SPECIFIC DATA ***************************************************/ p += doshort(p, &us); /* 249: Device/Token pairs XXVARIABLEXX */ for (i=0; i(buf), reinterpret_cast(p), us); buf[us]=0; p += us; /* What type? */ if (!strcmp(buf, "CRideSettingsConfig")) lastchart=type=CRIDESETTINGSCONFIG; if (!strcmp(buf, "CRideGoalConfig")) lastchart=type=CRIDEGOALCONFIG; if (!strcmp(buf, "CRideNotesConfig")) lastchart=type=CRIDENOTESCONFIG; if (!strcmp(buf, "CDistributionChartConfig")) lastchart=type=CDISTRIBUTIONCHARTCONFIG; if (!strcmp(buf, "CRideSummaryConfig")) lastchart=type=CRIDESUMMARYCONFIG; if (!strcmp(buf, "CMeanMaxChartConfig"))lastchart=type=CMEANMAXCHARTCONFIG; if (!strcmp(buf, "CMeanMaxChartCache")) lastchart=type=CMEANMAXCHARTCACHE; if (!strcmp(buf, "CDistributionChartCache")) lastchart=type=CDISTRIBUTIONCHARTCACHE; if (type == CDISTRIBUTIONCHARTCACHE) { p += 346; //p += donumber(p, &ul); /* always 2 */ //p += donumber(p, &ul); /* always 1 */ //_read(fd,buf,338); /* dist cache data */ /* handle "optional padding" */ p += optpad(p); } else if (type == CMEANMAXCHARTCACHE) { WKO_ULONG recs, term; p += 20; //_read(fd,buf,20); /* unknown 20 bytes */ p += donumber(p, &ul); p += (ul * 8); p += 28; //_read(fd,buf,28); /* further cached data */ p += donumber(p, &ul); p += (ul * 8); //_read(fd,buf,recs*8); /* first data block */ /* now its the main structures from a meanmax chart */ /* the code below is cut and paste from the meanmax chart * code below */ /* How many meanmax records in first set ? */ p += doshort(p, &us); recs = us; while (recs--) { p += donumber(p, &ul); if (!ul) goto next; else { /* Read that row */ p += 28; //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ //p += dodouble(p, &g); /* value! */ //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ } } /* 12 byte preamble */ p += 12; //p += donumber(p, &ul); //p += donumber(p, &ul); //p += donumber(p, &ul); /* How many meanmax records in second set ? */ p += doshort(p, &us); recs = us; term=1; while (recs--) { p += donumber(p, &ul); if (!ul) break; else { /* Read that row */ p += 28; //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ //p += dodouble(p, &g); /* value! */ //p += donumber(p, &ul); /* unknown */ //p += donumber(p, &ul); /* unknown */ } } /* as long as it didn't get terminated we need to read the end of the data */ if (term) p +=24; //_read(fd, buf, 24); /* might still be padding ? */ p += optpad(p); } else { p += donumber(p, &ul); /* always 2 */ p += dotext(p, &txtbuf[0]); /* perspective */ switch (type) { case CRIDESUMMARYCONFIG: p += donumber(p, &ul); p += donumber(p, &ul); z--; /* might still be padding ? */ p += optpad(p); break; case CRIDENOTESCONFIG: case CRIDEGOALCONFIG: p += donumber(p, &ul); case CRIDESETTINGSCONFIG: p += donumber(p, &ul); z--; case CDISTRIBUTIONCHARTCONFIG: case CMEANMAXCHARTCONFIG: /* already handled in previous if clause */ default: break; } } } else if (num==2) { /* Perspective */ p += dotext(p, &txtbuf[0]); } else if (num==3) { z--; /* Chart */ p += dotext(p, &txtbuf[0]); /* now lets skip the config and labels etc */ p += 32; //for (i=0; i<16; i++) doshort(fd,buf,0); /* what type is it? */ p += donumber(p, &ul); x = ul; if (x == 0x02) { /* Read through Distribution Chart */ p += 64; // /* distribution chart */ //p += donumber(p, &ul); /* chart type bin/pie1 */ //p += donumber(p, &ul); /* channel */ //p += donumber(p, &ul); /* percent/absolute */ //p += donumber(p, &ul); /* u1 */ //p += donumber(p, &ul); /* Units */ //p += donumber(p, &ul); /* Conversion type */ //p += dodouble(p, &g); /* first converter */ //p += donumber(p, &ul); /* bin mode auto/zone/manual */ ///* remaining 28 bytes of config data */ //p += donumber(p, &ul); /* include zeros */ //p += donumber(p, &ul); /* u2 */ //p += donumber(p, &ul); /* lower limit */ //p += donumber(p, &ul); /* u3 */ //p += donumber(p, &ul); /* upper limit */ //p += donumber(p, &ul); /* u4 */ //p += donumber(p, &ul); /* increments */ /* number of labels */ p += doshort(p, &us); for (i=0; i> 3]; // X>>3 is X/8 WKO_UCHAR bitmask = 1 << (bitoffset %8); // X&7 is X%8 return ((c & bitmask)!=0) ? 1 : 0; } unsigned int get_bits(WKO_UCHAR* data, unsigned bitOffset, unsigned numBits) { unsigned int bits = 0; unsigned int currentbit; bits=0; for (currentbit = bitOffset+numBits-1; numBits--; currentbit--) { bits = bits << 1; bits = bits | get_bit(data, currentbit); } return bits; } /***************************************************************************** * READ NUMBERS AND TEXTS FROM THE FILE, OPTIONALLY OUTPUTTING * IN DIFFERENT FORMATS TO DEBUG OR ANALYSE DATA * * dofloat() - read and retuen a 4 byte float * dodouble() - read and return an 8 byte double * donumber() - read and return a 4 byte long * doshort() - read and return a 2 byte short * dotext() - read a wko text (1byte len, n bytes string without terminator) * pbin() - print a number in binary ****************************************************************************/ unsigned int dofloat(WKO_UCHAR *p, float *pnum) { memcpy(pnum, p, 4); return 4; } unsigned int dodouble(WKO_UCHAR *p, double *pnum) { memcpy(pnum, p, 8); return 8; } unsigned int donumber(WKO_UCHAR *p, WKO_ULONG *pnum) { *pnum = qFromLittleEndian(p); return 4; } void pbin(WKO_UCHAR x) { static WKO_UCHAR bits[]={ 128, 64, 32, 16, 8, 4, 2, 1 }; int i; for (i=0; i<8; i++) printf("%c", ((x&bits[i]) == bits[i]) ? '1' : '0'); } unsigned int dotext(WKO_UCHAR *p, WKO_UCHAR *buf) { WKO_USHORT us; if (*p == 0) { *buf = '\0'; return 1; } else if (*p < 255) { strncpy(reinterpret_cast(buf), reinterpret_cast(p+1), *p); buf[*p]=0; return (*p)+1; } else { p += 1; p += doshort(p, &us); strncpy(reinterpret_cast(buf), reinterpret_cast(p), us); buf[us]=0; return (us)+3; } } unsigned int doshort(WKO_UCHAR *p, WKO_USHORT *pnum) { *pnum = qFromLittleEndian(p); return 2; }