// SPDX-License-Identifier: GPL-2.0 #ifdef __clang__ // Clang has a bug on zero-initialization of C structs. #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif #include "ssrf.h" #include #include #include #include #include #include #include #include "dive.h" #include "file.h" #include "units.h" #include "sha1.h" #include "gettext.h" #include "cochran.h" #include "divelist.h" #include #define POUND 0.45359237 #define FEET 0.3048 #define INCH 0.0254 #define GRAVITY 9.80665 #define ATM 101325.0 #define BAR 100000.0 #define FSW (ATM / 33.0) #define MSW (BAR / 10.0) #define PSI ((POUND * GRAVITY) / (INCH * INCH)) // Some say 0x4a14 and 0x4b14 are the right number for this offset // This works with CAN files from Analyst 4.01v and computers // such as Commander, Gemini, EMC-16, and EMC-20H #define LOG_ENTRY_OFFSET 0x4914 enum cochran_type { TYPE_GEMINI, TYPE_COMMANDER, TYPE_EMC }; struct config { enum cochran_type type; unsigned int logbook_size; unsigned int sample_size; } config; // Convert 4 bytes into an INT #define array_uint16_le(p) ((unsigned int) (p)[0] \ + ((p)[1]<<8) ) #define array_uint32_le(p) ((unsigned int) (p)[0] \ + ((p)[1]<<8) + ((p)[2]<<16) \ + ((p)[3]<<24)) /* * The Cochran file format is designed to be annoying to read. It's roughly: * * 0x00000: room for 65534 4-byte words, giving the starting offsets * of the dives themselves. * * 0x3fff8: the size of the file + 1 * 0x3ffff: 0 (high 32 bits of filesize? Bogus: the offsets into the file * are 32-bit, so it can't be a large file anyway) * * 0x40000: byte 0x46 * 0x40001: "block 0": 256 byte encryption key * 0x40101: the random modulus, or length of the key to use * 0x40102: block 1: Version and date of Analyst and a feature string identifying * the computer features and the features of the file * 0x40138: Computer configuration page 1, 512 bytes * 0x40338: Computer configuration page 2, 512 bytes * 0x40538: Misc data (tissues) 1500 bytes * 0x40b14: Ownership data 512 bytes ??? * * 0x4171c: Ownership data 512 bytes ??? * * 0x45415: Time stamp 17 bytes * 0x45426: Computer configuration page 1, 512 bytes * 0x45626: Computer configuration page 2, 512 bytes * */ static unsigned int partial_decode(unsigned int start, unsigned int end, const unsigned char *decode, unsigned offset, unsigned mod, const unsigned char *buf, unsigned int size, unsigned char *dst) { unsigned i, sum = 0; for (i = start; i < end; i++) { unsigned char d = decode[offset++]; if (i >= size) break; if (offset == mod) offset = 0; d += buf[i]; if (dst) dst[i] = d; sum += d; } return sum; } #ifdef COCHRAN_DEBUG #define hexchar(n) ("0123456789abcdef"[(n) & 15]) static int show_line(unsigned offset, const unsigned char *data, unsigned size, int show_empty) { unsigned char bits; int i, off; char buffer[120]; if (size > 16) size = 16; bits = 0; memset(buffer, ' ', sizeof(buffer)); off = sprintf(buffer, "%06x ", offset); for (i = 0; i < size; i++) { char *hex = buffer + off + 3 * i; char *asc = buffer + off + 50 + i; unsigned char byte = data[i]; hex[0] = hexchar(byte >> 4); hex[1] = hexchar(byte); bits |= byte; if (byte < 32 || byte > 126) byte = '.'; asc[0] = byte; asc[1] = 0; } if (bits) { puts(buffer); return 1; } if (show_empty) puts("..."); return 0; } static void cochran_debug_write(const unsigned char *data, unsigned size) { return; int show = 1, i; for (i = 0; i < size; i += 16) show = show_line(i, data + i, size - i, show); } static void cochran_debug_sample(const char *s, unsigned int sample_cnt) { switch (config.type) { case TYPE_GEMINI: switch (sample_cnt % 4) { case 0: printf("Hex: %02x %02x ", s[0], s[1]); break; case 1: printf("Hex: %02x %02x ", s[0], s[1]); break; case 2: printf("Hex: %02x %02x ", s[0], s[1]); break; case 3: printf("Hex: %02x %02x ", s[0], s[1]); break; } break; case TYPE_COMMANDER: switch (sample_cnt % 2) { case 0: printf("Hex: %02x %02x ", s[0], s[1]); break; case 1: printf("Hex: %02x %02x ", s[0], s[1]); break; } break; case TYPE_EMC: switch (sample_cnt % 2) { case 0: printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]); break; case 1: printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]); break; } break; } printf ("%02dh %02dm %02ds: Depth: %-5.2f, ", sample_cnt / 3660, (sample_cnt % 3660) / 60, sample_cnt % 60, depth); } #endif // COCHRAN_DEBUG static void cochran_parse_header(const unsigned char *decode, unsigned mod, const unsigned char *in, unsigned size) { unsigned char *buf = malloc(size); /* Do the "null decode" using a one-byte decode array of '\0' */ /* Copies in plaintext, will be overwritten later */ partial_decode(0, 0x0102, (const unsigned char *)"", 0, 1, in, size, buf); /* * The header scrambling is different form the dive * scrambling. Oh yay! */ partial_decode(0x0102, 0x010e, decode, 0, mod, in, size, buf); partial_decode(0x010e, 0x0b14, decode, 0, mod, in, size, buf); partial_decode(0x0b14, 0x1b14, decode, 0, mod, in, size, buf); partial_decode(0x1b14, 0x2b14, decode, 0, mod, in, size, buf); partial_decode(0x2b14, 0x3b14, decode, 0, mod, in, size, buf); partial_decode(0x3b14, 0x5414, decode, 0, mod, in, size, buf); partial_decode(0x5414, size, decode, 0, mod, in, size, buf); // Detect log type switch (buf[0x133]) { case '2': // Cochran Commander, version II log format config.logbook_size = 256; if (buf[0x132] == 0x10) { config.type = TYPE_GEMINI; config.sample_size = 2; // Gemini with tank PSI samples } else { config.type = TYPE_COMMANDER; config.sample_size = 2; // Commander } break; case '3': // Cochran EMC, version III log format config.type = TYPE_EMC; config.logbook_size = 512; config.sample_size = 3; break; default: printf ("Unknown log format v%c\n", buf[0x137]); free(buf); exit(1); break; } #ifdef COCHRAN_DEBUG puts("Header\n======\n\n"); cochran_debug_write(buf, size); #endif free(buf); } /* * Bytes expected after a pre-dive event code */ static int cochran_predive_event_bytes(unsigned char code) { int x = 0; int cmdr_event_bytes[15][2] = {{0x00, 16}, {0x01, 20}, {0x02, 17}, {0x03, 16}, {0x06, 18}, {0x07, 18}, {0x08, 18}, {0x09, 18}, {0x0a, 18}, {0x0b, 18}, {0x0c, 18}, {0x0d, 18}, {0x0e, 18}, {0x10, 20}, {-1, 0}}; int emc_event_bytes[15][2] = {{0x00, 18}, {0x01, 22}, {0x02, 19}, {0x03, 18}, {0x06, 20}, {0x07, 20}, {0x0a, 20}, {0x0b, 20}, {0x0f, 18}, {0x10, 20}, {-1, 0}}; switch (config.type) { case TYPE_GEMINI: case TYPE_COMMANDER: while (cmdr_event_bytes[x][0] != code && cmdr_event_bytes[x][0] != -1) x++; return cmdr_event_bytes[x][1]; break; case TYPE_EMC: while (emc_event_bytes[x][0] != code && emc_event_bytes[x][0] != -1) x++; return emc_event_bytes[x][1]; break; } return 0; } int cochran_dive_event_bytes(unsigned char event) { return (event == 0xAD || event == 0xAB) ? 4 : 0; } static void cochran_dive_event(struct divecomputer *dc, const unsigned char *s, unsigned int seconds, unsigned int *in_deco, unsigned int *deco_ceiling, unsigned int *deco_time) { switch (s[0]) { case 0xC5: // Deco obligation begins *in_deco = 1; add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); break; case 0xDB: // Deco obligation ends *in_deco = 0; add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, SAMPLE_FLAGS_END, 0, QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); break; case 0xAD: // Raise deco ceiling 10 ft *deco_ceiling -= 10; // ft *deco_time = (array_uint16_le(s + 3) + 1) * 60; break; case 0xAB: // Lower deco ceiling 10 ft *deco_ceiling += 10; // ft *deco_time = (array_uint16_le(s + 3) + 1) * 60; break; case 0xA8: // Entered Post Dive interval mode (surfaced) break; case 0xA9: // Exited PDI mode (re-submierged) break; case 0xBD: // Switched to normal PO2 setting break; case 0xC0: // Switched to FO2 21% mode (generally upon surface) break; case 0xC1: // "Ascent rate alarm add_event(dc, seconds, SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "ascent")); break; case 0xC2: // Low battery warning #ifdef SAMPLE_EVENT_BATTERY add_event(dc, seconds, SAMPLE_EVENT_BATTERY, SAMPLE_FLAGS_NONE, 0, QT_TRANSLATE_NOOP("gettextFromC", "battery")); #endif break; case 0xC3: // CNS warning add_event(dc, seconds, SAMPLE_EVENT_OLF, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "OLF")); break; case 0xC4: // Depth alarm begin add_event(dc, seconds, SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "maxdepth")); break; case 0xC8: // PPO2 alarm begin add_event(dc, seconds, SAMPLE_EVENT_PO2, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂")); break; case 0xCC: // Low cylinder 1 pressure"; break; case 0xCD: // Switch to deco blend setting add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, SAMPLE_FLAGS_NONE, 0, QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); break; case 0xCE: // NDL alarm begin add_event(dc, seconds, SAMPLE_EVENT_RBT, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "rbt")); break; case 0xD0: // Breathing rate alarm begin break; case 0xD3: // Low gas 1 flow rate alarm begin"; break; case 0xD6: // Ceiling alarm begin add_event(dc, seconds, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "ceiling")); break; case 0xD8: // End decompression mode *in_deco = 0; add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, SAMPLE_FLAGS_END, 0, QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); break; case 0xE1: // Ascent alarm end add_event(dc, seconds, SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_END, 0, QT_TRANSLATE_NOOP("gettextFromC", "ascent")); break; case 0xE2: // Low transmitter battery alarm add_event(dc, seconds, SAMPLE_EVENT_TRANSMITTER, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "transmitter")); break; case 0xE3: // Switch to FO2 mode break; case 0xE5: // Switched to PO2 mode break; case 0xE8: // PO2 too low alarm add_event(dc, seconds, SAMPLE_EVENT_PO2, SAMPLE_FLAGS_BEGIN, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂")); break; case 0xEE: // NDL alarm end add_event(dc, seconds, SAMPLE_EVENT_RBT, SAMPLE_FLAGS_END, 0, QT_TRANSLATE_NOOP("gettextFromC", "rbt")); break; case 0xEF: // Switch to blend 2 add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, SAMPLE_FLAGS_NONE, 0, QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); break; case 0xF0: // Breathing rate alarm end break; case 0xF3: // Switch to blend 1 (often at dive start) add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, SAMPLE_FLAGS_NONE, 0, QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); break; case 0xF6: // Ceiling alarm end add_event(dc, seconds, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_END, 0, QT_TRANSLATE_NOOP("gettextFromC", "ceiling")); break; default: break; } } /* * Parse sample data, extract events and build a dive */ static void cochran_parse_samples(struct dive *dive, const unsigned char *log, const unsigned char *samples, unsigned int size, unsigned int *duration, double *max_depth, double *avg_depth, double *min_temp) { const unsigned char *s; unsigned int offset = 0, profile_period = 1, sample_cnt = 0; double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0; int ascent_rate = 0; unsigned int ndl = 0; unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0; struct divecomputer *dc = &dive->dc; struct sample *sample; // Initialize stat variables *max_depth = 0, *avg_depth = 0, *min_temp = 0xFF; // Get starting depth and temp (tank PSI???) switch (config.type) { case TYPE_GEMINI: depth = (double) (log[CMD_START_DEPTH] + log[CMD_START_DEPTH + 1] * 256) / 4; temp = log[CMD_START_TEMP]; psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256; sgc_rate = (double)(log[CMD_START_SGC] + log[CMD_START_SGC + 1] * 256) / 2; profile_period = log[CMD_PROFILE_PERIOD]; break; case TYPE_COMMANDER: depth = (double) (log[CMD_START_DEPTH] + log[CMD_START_DEPTH + 1] * 256) / 4; temp = log[CMD_START_TEMP]; profile_period = log[CMD_PROFILE_PERIOD]; break; case TYPE_EMC: depth = (double) log [EMC_START_DEPTH] / 256 + log[EMC_START_DEPTH + 1]; temp = log[EMC_START_TEMP]; profile_period = log[EMC_PROFILE_PERIOD]; break; } // Skip past pre-dive events unsigned int x = 0; unsigned int c; while (x < size && (samples[x] & 0x80) == 0 && samples[x] != 0x40) { c = cochran_predive_event_bytes(samples[x]) + 1; #ifdef COCHRAN_DEBUG printf("Predive event: "); for (unsigned int y = 0; y < c && x + y < size; y++) printf("%02x ", samples[x + y]); putchar('\n'); #endif x += c; } // Now process samples offset = x; while (offset + config.sample_size < size) { s = samples + offset; // Start with an empty sample sample = prepare_sample(dc); sample->time.seconds = sample_cnt * profile_period; // Check for event if (s[0] & 0x80) { cochran_dive_event(dc, s, sample_cnt * profile_period, &in_deco, &deco_ceiling, &deco_time); offset += cochran_dive_event_bytes(s[0]) + 1; continue; } // Depth is in every sample depth_sample = (double)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); depth += depth_sample; #ifdef COCHRAN_DEBUG cochran_debug_sample(s, sample_cnt); #endif switch (config.type) { case TYPE_COMMANDER: switch (sample_cnt % 2) { case 0: // Ascent rate ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); break; case 1: // Temperature temp = s[1] / 2 + 20; break; } break; case TYPE_GEMINI: // Gemini with tank pressure and SAC rate. switch (sample_cnt % 4) { case 0: // Ascent rate ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1); break; case 2: // PSI change psi -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4; break; case 1: // SGC rate sgc_rate -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2; break; case 3: // Temperature temp = (double)s[1] / 2 + 20; break; } break; case TYPE_EMC: switch (sample_cnt % 2) { case 0: // Ascent rate ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); break; case 1: // Temperature temp = (double)s[1] / 2 + 20; break; } // Get NDL and deco information switch (sample_cnt % 24) { case 20: if (offset + 5 < size) { if (in_deco) { // Fist stop time //first_deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds ndl = 0; } else { // NDL ndl = (s[2] + s[5] * 256 + 1) * 60; // seconds deco_time = 0; } } break; case 22: if (offset + 5 < size) { if (in_deco) { // Total stop time deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds ndl = 0; } } break; } } // Track dive stats if (depth > *max_depth) *max_depth = depth; if (temp < *min_temp) *min_temp = temp; *avg_depth = (*avg_depth * sample_cnt + depth) / (sample_cnt + 1); sample->depth.mm = lrint(depth * FEET * 1000); sample->ndl.seconds = ndl; sample->in_deco = in_deco; sample->stoptime.seconds = deco_time; sample->stopdepth.mm = lrint(deco_ceiling * FEET * 1000); sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8); sample->sensor[0] = 0; sample->pressure[0].mbar = lrint(psi * PSI / 100); finish_sample(dc); offset += config.sample_size; sample_cnt++; } UNUSED(ascent_rate); // mark the variable as unused if (sample_cnt > 0) *duration = sample_cnt * profile_period - 1; } static void cochran_parse_dive(const unsigned char *decode, unsigned mod, const unsigned char *in, unsigned size, struct dive_table *table) { unsigned char *buf = malloc(size); struct dive *dive; struct divecomputer *dc; struct tm tm = {0}; uint32_t csum[5]; double max_depth, avg_depth, min_temp; unsigned int duration = 0, corrupt_dive = 0; /* * The scrambling has odd boundaries. I think the boundaries * match some data structure size, but I don't know. They were * discovered the same way we dynamically discover the decode * size: automatically looking for least random output. * * The boundaries are also this confused "off-by-one" thing, * the same way the file size is off by one. It's as if the * cochran software forgot to write one byte at the beginning. */ partial_decode(0, 0x0fff, decode, 1, mod, in, size, buf); partial_decode(0x0fff, 0x1fff, decode, 0, mod, in, size, buf); partial_decode(0x1fff, 0x2fff, decode, 0, mod, in, size, buf); partial_decode(0x2fff, 0x48ff, decode, 0, mod, in, size, buf); /* * This is not all the descrambling you need - the above are just * what appears to be the fixed-size blocks. The rest is also * scrambled, but there seems to be size differences in the data, * so this just descrambles part of it: */ if (size < 0x4914 + config.logbook_size) { // Analyst calls this a "Corrupt Beginning Summary" free(buf); return; } // Decode log entry (512 bytes + random prefix) partial_decode(0x48ff, 0x4914 + config.logbook_size, decode, 0, mod, in, size, buf); unsigned int sample_size = size - 0x4914 - config.logbook_size; int g; unsigned int sample_pre_offset = 0, sample_end_offset = 0; // Decode sample data partial_decode(0x4914 + config.logbook_size, size, decode, 0, mod, in, size, buf); #ifdef COCHRAN_DEBUG // Display pre-logbook data puts("\nPre Logbook Data\n"); cochran_debug_write(buf, 0x4914); // Display log book puts("\nLogbook Data\n"); cochran_debug_write(buf + 0x4914, config.logbook_size + 0x400); // Display sample data puts("\nSample Data\n"); #endif dive = alloc_dive(); dc = &dive->dc; unsigned char *log = (buf + 0x4914); switch (config.type) { case TYPE_GEMINI: case TYPE_COMMANDER: if (config.type == TYPE_GEMINI) { cylinder_t cyl = empty_cylinder; dc->model = "Gemini"; dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no fill_default_cylinder(dive, &cyl); cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256 + log[CMD_O2_PERCENT + 1]) * 10; cyl.gasmix.he.permille = 0; add_cylinder(&dive->cylinders, 0, cyl); } else { dc->model = "Commander"; dc->deviceid = array_uint32_le(buf + 0x31e); // serial no for (g = 0; g < 2; g++) { cylinder_t cyl = empty_cylinder; fill_default_cylinder(dive, &cyl); cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256 + log[CMD_O2_PERCENT + g * 2 + 1]) * 10; cyl.gasmix.he.permille = 0; add_cylinder(&dive->cylinders, g, cyl); } } tm.tm_year = log[CMD_YEAR]; tm.tm_mon = log[CMD_MON] - 1; tm.tm_mday = log[CMD_DAY]; tm.tm_hour = log[CMD_HOUR]; tm.tm_min = log[CMD_MIN]; tm.tm_sec = log[CMD_SEC]; tm.tm_isdst = -1; dive->when = dc->when = utc_mktime(&tm); dive->number = log[CMD_NUMBER] + log[CMD_NUMBER + 1] * 256 + 1; dc->duration.seconds = (log[CMD_BT] + log[CMD_BT + 1] * 256) * 60; dc->surfacetime.seconds = (log[CMD_SIT] + log[CMD_SIT + 1] * 256) * 60; dc->maxdepth.mm = lrint((log[CMD_MAX_DEPTH] + log[CMD_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000); dc->meandepth.mm = lrint((log[CMD_AVG_DEPTH] + log[CMD_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000); dc->watertemp.mkelvin = C_to_mkelvin((log[CMD_MIN_TEMP] / 32) - 1.8); dc->surface_pressure.mbar = lrint(ATM / BAR * pow(1 - 0.0000225577 * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000); dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY]; SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum); dc->diveid = csum[0]; if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff) corrupt_dive = 1; sample_pre_offset = array_uint32_le(log + CMD_PREDIVE_OFFSET); sample_end_offset = array_uint32_le(log + CMD_END_OFFSET); break; case TYPE_EMC: dc->model = "EMC"; dc->deviceid = array_uint32_le(buf + 0x31e); // serial no for (g = 0; g < 4; g++) { cylinder_t cyl = empty_cylinder; fill_default_cylinder(dive, &cyl); cyl.gasmix.o2.permille = (log[EMC_O2_PERCENT + g * 2] / 256 + log[EMC_O2_PERCENT + g * 2 + 1]) * 10; cyl.gasmix.he.permille = (log[EMC_HE_PERCENT + g * 2] / 256 + log[EMC_HE_PERCENT + g * 2 + 1]) * 10; add_cylinder(&dive->cylinders, g, cyl); } tm.tm_year = log[EMC_YEAR]; tm.tm_mon = log[EMC_MON] - 1; tm.tm_mday = log[EMC_DAY]; tm.tm_hour = log[EMC_HOUR]; tm.tm_min = log[EMC_MIN]; tm.tm_sec = log[EMC_SEC]; tm.tm_isdst = -1; dive->when = dc->when = utc_mktime(&tm); dive->number = log[EMC_NUMBER] + log[EMC_NUMBER + 1] * 256 + 1; dc->duration.seconds = (log[EMC_BT] + log[EMC_BT + 1] * 256) * 60; dc->surfacetime.seconds = (log[EMC_SIT] + log[EMC_SIT + 1] * 256) * 60; dc->maxdepth.mm = lrint((log[EMC_MAX_DEPTH] + log[EMC_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000); dc->meandepth.mm = lrint((log[EMC_AVG_DEPTH] + log[EMC_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000); dc->watertemp.mkelvin = C_to_mkelvin((log[EMC_MIN_TEMP] - 32) / 1.8); dc->surface_pressure.mbar = lrint(ATM / BAR * pow(1 - 0.0000225577 * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000); dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum); dc->diveid = csum[0]; if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff) corrupt_dive = 1; sample_pre_offset = array_uint32_le(log + EMC_PREDIVE_OFFSET); sample_end_offset = array_uint32_le(log + EMC_END_OFFSET); break; } // Use the log information to determine actual profile sample size // Otherwise we will get surface time at end of dive. if (sample_pre_offset < sample_end_offset && sample_end_offset != 0xffffffff) sample_size = sample_end_offset - sample_pre_offset; cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914 + config.logbook_size, sample_size, &duration, &max_depth, &avg_depth, &min_temp); // Check for corrupt dive if (corrupt_dive) { dc->maxdepth.mm = lrint(max_depth * FEET * 1000); dc->meandepth.mm = lrint(avg_depth * FEET * 1000); dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8); dc->duration.seconds = duration; } record_dive_to_table(dive, table); free(buf); } int try_to_open_cochran(const char *filename, struct memblock *mem, struct dive_table *table, struct trip_table *trips, struct dive_site_table *sites) { UNUSED(filename); UNUSED(trips); UNUSED(sites); unsigned int i; unsigned int mod; unsigned int *offsets, dive1, dive2; unsigned char *decode = mem->buffer + 0x40001; if (mem->size < 0x40000) return 0; offsets = (unsigned int *) mem->buffer; dive1 = offsets[0]; dive2 = offsets[1]; if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size) return 0; mod = decode[0x100] + 1; cochran_parse_header(decode, mod, mem->buffer + 0x40000, dive1 - 0x40000); // Decode each dive for (i = 0; i < 65534; i++) { dive1 = offsets[i]; dive2 = offsets[i + 1]; if (dive2 < dive1) break; if (dive2 > mem->size) break; cochran_parse_dive(decode, mod, mem->buffer + dive1, dive2 - dive1, table); } return 1; // no further processing needed }