summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--cochran.c793
-rw-r--r--cochran_cmdr.h82
-rw-r--r--cochran_emc.h171
-rw-r--r--file.c4
-rw-r--r--file.h4
-rw-r--r--qt-ui/mainwindow.cpp2
-rw-r--r--subsurface.pro3
8 files changed, 932 insertions, 128 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3298981e5..a04794a26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@ ADD_CUSTOM_TARGET(version ALL COMMAND
# compile the core library, in C.
SET(SUBSURFACE_CORE_LIB_SRCS
+ cochran.c
deco.c
device.c
dive.c
diff --git a/cochran.c b/cochran.c
index 018ca673a..b309ecb10 100644
--- a/cochran.c
+++ b/cochran.c
@@ -1,3 +1,4 @@
+#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -7,8 +8,48 @@
#include "dive.h"
#include "file.h"
-
-#define DON
+#include "units.h"
+#include "gettext.h"
+#include "cochran_emc.h"
+#include "cochran_cmdr.h"
+#include "divelist.h"
+
+#include <libdivecomputer/parser.h>
+
+#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:
@@ -20,18 +61,26 @@
* 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: "block 0": the decoding block. The first byte is some random
- * value (0x46 in the files I have access to), the next 200+ bytes or so
- * are the "scrambling array" that needs to be added into the file
- * contents to make sense of them.
+ * 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 ??? <copy>
+ *
+ * 0x45415: Time stamp 17 bytes
+ * 0x45426: Computer configuration page 1, 512 bytes <copy>
+ * 0x45626: Computer configuration page 2, 512 bytes <copy>
*
- * The descrambling array seems to be of some random size which is likely
- * determinable from the array somehow, the two test files I have it as
- * 230 bytes and 234 bytes respectively.
*/
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)
+ const unsigned char *decode, unsigned offset, unsigned mod,
+ const unsigned char *buf, unsigned int size, unsigned char *dst)
{
unsigned i, sum = 0;
@@ -49,39 +98,12 @@ static unsigned int partial_decode(unsigned int start, unsigned int end,
return sum;
}
-/*
- * The decode buffer size can be figured out by simply trying our the
- * decode: we expect that the scrambled contents are largely random, and
- * thus tend to have half the bits set. Summing over the bytes is going
- * to give an average of 0x80 per byte.
- *
- * The decoded array is mostly full of zeroes, so the sum is lower.
- *
- * Works for me.
- */
-static int figure_out_modulus(const unsigned char *decode, const unsigned char *dive, unsigned int size)
-{
- int mod, best = -1;
- unsigned int min = ~0u;
-
- if (size < 0x1000)
- return best;
-
- for (mod = 50; mod < 300; mod++) {
- unsigned int sum;
-
- sum = partial_decode(0, 0x0fff, decode, 1, mod, dive, size, NULL);
- if (sum < min) {
- min = sum;
- best = mod;
- }
- }
- return best;
-}
+#ifdef COCHRAN_DEBUG
#define hexchar(n) ("0123456789abcdef"[(n) & 15])
-static int show_line(unsigned offset, const unsigned char *data, unsigned size, int show_empty)
+static int show_line(unsigned offset, const unsigned char *data,
+ unsigned size, int show_empty)
{
unsigned char bits;
int i, off;
@@ -116,108 +138,503 @@ static int show_line(unsigned offset, const unsigned char *data, unsigned size,
return 0;
}
-static void cochran_debug_write(const char *filename, const unsigned char *data, unsigned size)
+static void cochran_debug_write(const unsigned char *data, unsigned size)
{
- int i, show = 1;
+ return;
+
+ int show = 1, i;
+
for (i = 0; i < size; i += 16)
show = show_line(i, data + i, size - i, show);
}
-static void parse_cochran_header(const char *filename,
+static void cochran_debug_sample(const char * s, unsigned int seconds)
+{
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ switch (seconds % 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 (seconds % 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 (seconds % 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, ", seconds / 3660,
+ (seconds % 3660) / 60, seconds % 60, depth);
+}
+
+#endif // COCHRAN_DEBUG
+
+static void cochran_parse_header(
const unsigned char *decode, unsigned mod,
const unsigned char *in, unsigned size)
{
- char *buf = malloc(size);
+ unsigned char *buf = malloc(size);
/* Do the "null decode" using a one-byte decode array of '\0' */
- partial_decode(0, 0x0b14, "", 0, 1, in, size, buf);
+ /* Copies in plaintext, will be overwritten later */
+ partial_decode(0, 0x0102, "", 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);
+ 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]);
+ exit(1);
+ break;
+ }
- printf("\n%s, header\n\n", filename);
- cochran_debug_write(filename, buf, size);
+#ifdef COCHRAN_DEBUG
+ puts("Header\n======\n\n");
+ cochran_debug_write(buf, size);
+#endif
free(buf);
}
+
/*
- * Cochran export files show that depths seem to be in
- * quarter feet (rounded up to tenths).
- *
- * Temperature seems to be exported in Fahrenheit.
- *
- * Cylinder pressure seems to be in multiples of 4 psi.
- *
- * The data seems to be some byte-stream where the pattern
- * appears to be that the two high bits indicate type of
- * data.
- *
- * For '00', the low six bits seem to be positive
- * values with a distribution towards zero, probably depth
- * deltas. '0 0' exists, but is very rare ("surface"?). 63
- * exists, but is rare.
- *
- * For '01', the low six bits seem to be a signed binary value,
- * with the most common being 0, and 1 and -1 (63) being the
- * next most common values.
- *
- * NOTE! Don's CAN data is different. It shows the reverse pattern
- * for 00 and 01 above: 00 looks like signed data, with 01 looking
- * like unsigned data.
- *
- * For '10', there seems to be another positive value distribution,
- * but unlike '00' the value 0 is common, and I see examples of 63
- * too ("overflow"?) and a spike at '7'.
- *
- * Again, Don's data is different.
- *
- * The values for '11' seem to be some exception case. Possibly
- * overflow handling, possibly warning events. It doesn't have
- * any clear distribution: values 0, 1, 16, 33, 35, 48, 51, 55
- * and 63 are common.
- *
- * For David and Don's data, '01' is the most common, with '00'
- * and '10' not uncommon. '11' is two orders of magnitude less
- * common.
- *
- * For Alex, '00' is the most common, with 01 about a third as
- * common, and 02 a third of that. 11 is least common.
- *
- * There clearly are variations in the format here. And Alex has
- * a different data offset than Don/David too (see the #ifdef DON).
- * Christ. Maybe I've misread the patterns entirely.
- */
-static void cochran_profile_write(const unsigned char *buf, int size)
+* Bytes expected after a pre-dive event code
+*/
+
+static int cochran_predive_event_bytes (unsigned char code)
{
- int i;
- for (i = 0; i < size; i++) {
- unsigned char c = buf[i];
- printf("%d %d\n",
- c >> 6, c & 0x3f);
+ int x = 0;
+
+ int gem_event_bytes[15][2] = { {0x00, 10}, {0x02, 17}, {0x08, 18},
+ {0x09, 18}, {0x0c, 18}, {0x0d, 18},
+ {0x0e, 18},
+ { -1, 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, 20}, {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:
+ while (gem_event_bytes[x][0] != code && gem_event_bytes[x][0] != -1)
+ x++;
+
+ return gem_event_bytes[x][1];
+ break;
+ 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;
}
+
}
-static void parse_cochran_dive(const char *filename, int dive,
- const unsigned char *decode, unsigned mod,
- const unsigned char *in, unsigned size)
+int cochran_dive_event_bytes(unsigned char event)
{
- char *buf = malloc(size);
-#ifdef DON
- unsigned int offset = 0x4a14;
-#else
- unsigned int offset = 0x4b14;
+ if (event == 0xAD || event == 0xAB)
+ return 4;
+ else
+ return 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", "PO2"));
+ 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", "PO2"));
+ 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, int size,
+ unsigned int *duration, double *max_depth,
+ double *avg_depth, double *min_temp)
+{
+ const unsigned char *s;
+ unsigned int offset = 0, seconds = 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;
+
+ const struct cochran_cmdr_log_t *log_cmdr = (struct cochran_cmdr_log_t *) log;
+ const struct cochran_emc_log_t *log_emc = (struct cochran_emc_log_t *) log;
+
+ // 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 = (float) (log_cmdr->start_depth[0]
+ + log_cmdr->start_depth[1] * 256) / 4;
+ psi = log_cmdr->start_psi[0] + log_cmdr->start_psi[1] * 256;
+ sgc_rate = (float) (log_cmdr->start_sgc[0]
+ + log_cmdr->start_sgc[1] * 256) / 2;
+ break;
+
+ case TYPE_COMMANDER:
+ depth = (float) (log_cmdr->start_depth[0]
+ + log_cmdr->start_depth[1] * 256) / 4;
+ break;
+
+ case TYPE_EMC:
+ depth = (float) log_emc->start_depth[0] / 256
+ + log_emc->start_depth[1];
+ temp = log_emc->start_temperature;
+ break;
+ }
+
+ // Skip past pre-dive events
+ unsigned int x = 0;
+ if (samples[x] != 0x40) {
+ unsigned int c;
+ while ( (samples[x] & 0x80) == 0 && samples[x] != 0x40 && x < size) {
+ c = cochran_predive_event_bytes(samples[x]) + 1;
+#ifdef COCHRAN_DEBUG
+ printf ("Predive event: ", samples[x]);
+ for (int y = 0; y < c; y++) printf ("%02x ", samples[x + y]);
+ putchar('\n');
+#endif
+ x += c;
+ }
+ }
+
+ // Now process samples
+ offset = x;
+ while (offset < size) {
+ s = samples + offset;
+
+ // Start with an empty sample
+ sample = prepare_sample(dc);
+ sample->time.seconds = seconds;
+
+ // Check for event
+ if (s[0] & 0x80) {
+ cochran_dive_event(dc, s, seconds, &in_deco, &deco_ceiling, &deco_time);
+ offset += cochran_dive_event_bytes(s[0]) + 1;
+ continue;
+ }
+
+ // Depth is in every sample
+ depth_sample = (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1);
+ depth += depth_sample;
+
+#ifdef COCHRAN_DEBUG
+ cochran_debug_sample(s, seconds);
+#endif
+
+ switch (config.type)
+ {
+ case TYPE_COMMANDER:
+ switch (seconds % 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 (seconds % 4)
+ {
+ case 0: // Ascent rate
+ ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1);
+ break;
+ case 2: // PSI change
+ psi -= (float) (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4;
+ break;
+ case 1: // SGC rate
+ sgc_rate -= (float) (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2;
+ break;
+ case 3: // Temperature
+ temp = (float) s[1] / 2 + 20;
+ break;
+ }
+ break;
+ case TYPE_EMC:
+ switch (seconds % 2)
+ {
+ case 0: // Ascent rate
+ ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1);
+ break;
+ case 1: // Temperature
+ temp = (float) s[1] / 2 + 20;
+ break;
+ }
+ // Get NDL and deco information
+ switch (seconds % 24)
+ {
+ case 20:
+ 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 (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 * seconds + depth) / (seconds + 1);
+
+ sample->depth.mm = depth * FEET * 1000;
+ sample->ndl.seconds = ndl;
+ sample->in_deco = in_deco;
+ sample->stoptime.seconds = deco_time;
+ sample->stopdepth.mm = deco_ceiling * FEET * 1000;
+ sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8);
+ sample->sensor = 0;
+ sample->cylinderpressure.mbar = psi * PSI / 100;
+
+ finish_sample(dc);
+
+ offset += config.sample_size;
+ seconds++;
+ }
+
+ if (seconds > 0)
+ *duration = seconds - 1;
+}
+
+
+static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
+ const unsigned char *in, unsigned size)
+{
+ 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
@@ -240,17 +657,148 @@ static void parse_cochran_dive(const char *filename, int dive,
* scrambled, but there seems to be size differences in the data,
* so this just descrambles part of it:
*/
- partial_decode(0x48ff, offset, decode, 0, mod, in, size, buf);
- partial_decode(offset, size, decode, 0, mod, in, size, buf);
+ // 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;
- printf("\n%s, dive %d\n\n", filename, dive);
- cochran_debug_write(filename, buf, size);
- cochran_profile_write(buf + offset, size - offset);
+ // 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;
+
+ struct cochran_cmdr_log_t *cmdr_log = (struct cochran_cmdr_log_t *) (buf + 0x4914);
+ struct cochran_emc_log_t *emc_log = (struct cochran_emc_log_t *) (buf + 0x4914);
+
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ case TYPE_COMMANDER:
+ if (config.type == TYPE_GEMINI) {
+ dc->model = "Gemini";
+ dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no
+ fill_default_cylinder(&dive->cylinder[0]);
+ dive->cylinder[0].gasmix.o2.permille = (cmdr_log->o2_percent[0][0] / 256
+ + cmdr_log->o2_percent[0][1]) * 10;
+ dive->cylinder[0].gasmix.he.permille = 0;
+ } else {
+ dc->model = "Commander";
+ dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
+ for (g = 0; g < 2; g++) {
+ fill_default_cylinder(&dive->cylinder[g]);
+ dive->cylinder[g].gasmix.o2.permille = (cmdr_log->o2_percent[g][0] / 256
+ + cmdr_log->o2_percent[g][1]) * 10;
+ dive->cylinder[g].gasmix.he.permille = 0;
+ }
+ }
+
+ tm.tm_year = cmdr_log->year;
+ tm.tm_mon = cmdr_log->month - 1;
+ tm.tm_mday = cmdr_log->day;
+ tm.tm_hour = cmdr_log->hour;
+ tm.tm_min = cmdr_log->minutes;
+ tm.tm_sec = cmdr_log->seconds;
+ tm.tm_isdst = -1;
+
+ dive->when = dc->when = utc_mktime(&tm);
+ dive->number = cmdr_log->number[0] + cmdr_log->number[1] * 256 + 1;
+ dc->duration.seconds = (cmdr_log->bt[0] + cmdr_log->bt[1] * 256) * 60;
+ dc->surfacetime.seconds = (cmdr_log->sit[0] + cmdr_log->sit[1] * 256) * 60;
+ dc->maxdepth.mm = (cmdr_log->max_depth[0] +
+ cmdr_log->max_depth[1] * 256) / 4 * FEET * 1000;
+ dc->meandepth.mm = (cmdr_log->avg_depth[0] +
+ cmdr_log->avg_depth[1] * 256) / 4 * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((cmdr_log->temp / 32) - 1.8);
+ dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577
+ * (double) cmdr_log->altitude * 250 * FEET, 5.25588) * 1000;
+ dc->salinity = 10000 + 150 * emc_log->water_conductivity;
+
+ SHA1(cmdr_log->number, 2, (unsigned char *)csum);
+ dc->diveid = csum[0];
+
+ if (cmdr_log->max_depth[0] == 0xff && cmdr_log->max_depth[1] == 0xff)
+ corrupt_dive = 1;
+
+ break;
+ case TYPE_EMC:
+ dc->model = "EMC";
+ dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
+ for (g = 0; g < 4; g++) {
+ fill_default_cylinder(&dive->cylinder[g]);
+ dive->cylinder[g].gasmix.o2.permille = (emc_log->o2_percent[g][0] / 256
+ + emc_log->o2_percent[g][1]) * 10;
+ dive->cylinder[g].gasmix.he.permille = (emc_log->he_percent[g][0] / 256
+ + emc_log->he_percent[g][1]) * 10;
+ }
+
+ tm.tm_year = emc_log->year;
+ tm.tm_mon = emc_log->month - 1;
+ tm.tm_mday = emc_log->day;
+ tm.tm_hour = emc_log->hour;
+ tm.tm_min = emc_log->minutes;
+ tm.tm_sec = emc_log->seconds;
+ tm.tm_isdst = -1;
+
+
+ dive->when = dc->when = utc_mktime(&tm);
+ dive->number = emc_log->number[0] + emc_log->number[1] * 256 + 1;
+ dc->duration.seconds = (emc_log->bt[0] + emc_log->bt[1] * 256) * 60;
+ dc->surfacetime.seconds = (emc_log->sit[0] + emc_log->sit[1] * 256) * 60;
+ dc->maxdepth.mm = (emc_log->max_depth[0] +
+ emc_log->max_depth[1] * 256) / 4 * FEET * 1000;
+ dc->meandepth.mm = (emc_log->avg_depth[0] +
+ emc_log->avg_depth[1] * 256) / 4 * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((emc_log->temp - 32) / 1.8);
+ dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577
+ * (double) emc_log->altitude * 250 * FEET, 5.25588) * 1000;
+ dc->salinity = 10000 + 150 * emc_log->water_conductivity;
+
+ SHA1(emc_log->number, 2, (unsigned char *)csum);
+ dc->diveid = csum[0];
+
+ if (emc_log->max_depth[0] == 0xff && emc_log->max_depth[1] == 0xff)
+ corrupt_dive = 1;
+
+ break;
+ }
+
+ 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 = max_depth * FEET * 1000;
+ dc->meandepth.mm = avg_depth * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8);
+ dc->duration.seconds = duration;
+ }
+
+ dive->downloaded = true;
+ record_dive(dive);
+ mark_divelist_changed(true);
free(buf);
}
-int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error)
+int try_to_open_cochran(const char *filename, struct memblock *mem)
{
unsigned int i;
unsigned int mod;
@@ -259,16 +807,19 @@ int try_to_open_cochran(const char *filename, struct memblock *mem, GError **err
if (mem->size < 0x40000)
return 0;
- offsets = mem->buffer;
+
+ offsets = (int *) mem->buffer;
dive1 = offsets[0];
dive2 = offsets[1];
+
if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size)
return 0;
- mod = figure_out_modulus(decode, mem->buffer + dive1, dive2 - dive1);
+ mod = decode[0x100] + 1;
- parse_cochran_header(filename, decode, mod, mem->buffer + 0x40000, dive1 - 0x40000);
+ 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];
@@ -276,8 +827,10 @@ int try_to_open_cochran(const char *filename, struct memblock *mem, GError **err
break;
if (dive2 > mem->size)
break;
- parse_cochran_dive(filename, i + 1, decode, mod, mem->buffer + dive1, dive2 - dive1);
+
+ cochran_parse_dive(decode, mod, mem->buffer + dive1,
+ dive2 - dive1);
}
- exit(0);
+ return 1; // no further processing needed
}
diff --git a/cochran_cmdr.h b/cochran_cmdr.h
new file mode 100644
index 000000000..1c5f938d5
--- /dev/null
+++ b/cochran_cmdr.h
@@ -0,0 +1,82 @@
+/*
+ * subsurface
+ *
+ * Copyright (C) 2014 John Van Ostrand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+
+struct cochran_cmdr_log_t {
+ // Pre-dive 128 bytes
+ unsigned char minutes, seconds; // 2 bytes
+ unsigned char day, hour, year, month; // 4 bytes
+ unsigned char sample_start_offset[4]; // 4 bytes
+ unsigned char start_timestamp[4]; // 4 bytes
+ unsigned char pre_dive_timestamp[4]; // 4 bytes
+ unsigned char unknown1[6]; // 6 bytes
+ unsigned char water_conductivity; // 1 byte [0=low, 2=high]
+ unsigned char unknown2[5]; // 5 bytes
+//30
+ unsigned char sample_pre_event_offset[4];// 4 bytes
+ unsigned char unknown3[4]; // 4 bytes
+ unsigned char start_battery_voltage[2]; // 2 bytes [/256]
+//40
+ unsigned char unknown4[2]; // 2 bytes
+ unsigned char start_sgc[2]; // 2 bytes
+ unsigned char entered_or_computed_po[2];// 2 bytes ???
+ unsigned char unknown5[10]; // 10 bytes
+//56
+ unsigned char start_depth[2]; // 2 byte [/4]
+ unsigned char unknown6[4]; // 3 bytes
+ unsigned char start_psi[2]; // 2 bytes LE
+ unsigned char unknown7[4]; // 4 bytes
+ unsigned char sit[2]; // 2 bytes
+//70
+ unsigned char number[2]; // 2 bytes
+ unsigned char unknown8[1]; // 1 byte
+ unsigned char altitude; // 1 byte [/4 = kft]
+ unsigned char unknown9[28]; // 27 bytes
+ unsigned char alarm_depth[2]; // 2 bytes
+ unsigned char unknown10[4]; // 5 bytes
+//108
+ unsigned char repetitive_dive; // 1 byte
+ unsigned char unknown11[3]; // 3 bytes
+ unsigned char start_tissue_nsat[16]; // 16 bytes [/256]
+
+ // Post-dive 128 bytes
+ unsigned char sample_end_offset[4]; // 4 bytes
+ unsigned char unknown12[21]; // 21 bytes
+ unsigned char temp; // 1 byte
+ unsigned char unknown13[12]; // 12 bytes
+ unsigned char bt[2]; // 2 bytes [minutes]
+//168
+ unsigned char max_depth[2]; // 2 bytes [/4]
+ unsigned char avg_depth[2]; // 2 bytes
+ unsigned char unknown14[38]; // 38 bytes
+//210
+ unsigned char o2_percent[4][2]; // 8 bytes
+ unsigned char unknown15[22]; // 22 bytes
+ unsigned char end_tissue_nsat[16]; // 16 bytes [/256]
+} __attribute__((packed));
+
+struct cochran_cmdr_config1_t {
+ unsigned char unknown1[209];
+ unsigned short int dive_count;
+ unsigned char unknown2[274];
+ unsigned short int serial_num; // @170
+ unsigned char unknown3[24];
+} __attribute__((packed));
diff --git a/cochran_emc.h b/cochran_emc.h
new file mode 100644
index 000000000..46a56fe68
--- /dev/null
+++ b/cochran_emc.h
@@ -0,0 +1,171 @@
+/*
+ * subsurface
+ *
+ * Copyright (C) 2014 John Van Ostrand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+// 512 bytes for each dive in the log book
+struct cochran_emc_log_t {
+ // Pre-dive 256 bytes
+ unsigned char seconds, minutes, hour; // 3 bytes
+ unsigned char day, month, year; // 3 bytes
+ unsigned char sample_start_offset[4]; // 4 bytes
+ unsigned char start_timestamp[4]; // 4 bytes [secs from jan 1,92]
+ unsigned char pre_dive_timestamp[4]; // 4 bytes [secs from Jan 1,92]
+ unsigned char unknown1[6]; // 6 bytes
+ unsigned char water_conductivity; // 1 byte [0 =low, 2-high]
+ unsigned char unknown2[5]; // 5 bytes
+//30
+ unsigned char sample_pre_event_offset[4]; // 4 bytes
+ unsigned char config_bitfield[6]; // 6 bytes
+ unsigned char unknown3[2]; // 2 bytes
+ unsigned char start_depth[2]; // 2 bytes [/256]
+ unsigned char unknown4[2]; // 2 bytes
+ unsigned char start_battery_voltage[2]; // 2 bytes [/256]
+//48
+ unsigned char unknown5[7]; // 7 bytes
+ unsigned char start_temperature; // 1 byte [F]
+ unsigned char unknown6[28]; // 28 bytes
+ unsigned char sit[2]; // 2 bytes [minutes]
+ unsigned char number[2]; // 2 bytes
+ unsigned char unknown7[1]; // 1 bytes
+ unsigned char altitude; // 1 byte [/4 = kft]
+ unsigned char start_nofly[2]; // 2 bytes [/256 = hours]
+//92
+ unsigned char unknown8[18]; // 18 bytes
+ unsigned char post_dive_sit[2]; // 2 bytes [seconds]
+ unsigned char po2_set_point[9][2]; // 18 bytes [/256 = %]
+ unsigned char unknown9[12]; // 12 bytes
+ unsigned char po2_alarm[2]; // 2 bytes [/256 = %]
+//144
+ unsigned char o2_percent[10][2]; // 20 bytes [/256 = %]
+ unsigned char he_percent[10][2]; // 20 bytes [/256 = %]
+ unsigned char alarm_depth[2]; // 2 bytes
+ unsigned char unknown10[14]; // 14 bytes
+ unsigned char conservatism; // 1 bytes [/256 = fraction]
+ unsigned char unknown11[2]; // 2 bytes
+ unsigned char repetitive_dive; // 1 byte
+ unsigned char unknown12[12]; // 12 bytes
+ unsigned char start_tissue_nsat[20][2]; // 40 bytes [/256]
+
+ // Post-dive 256 bytes
+ unsigned char sample_end_offset[4]; // 4 bytes
+ unsigned char unknown13[33]; // 33 bytes
+ unsigned char temp; // 1 byte [F]
+ unsigned char unknown14[10]; // 10 bytes
+// 48
+ unsigned char bt[2]; // 2 bytes [minutes]
+ unsigned char max_depth[2]; // 2 bytes [/4 = ft]
+ unsigned char unknown15[2]; // 2 bytes
+ unsigned char avg_depth[2]; // 2 bytes [/4 = ft]
+ unsigned char min_ndc[2]; // 2 bytes [minutes]
+ unsigned char min_ndx_bt[2]; // 2 bytes [minutes]
+ unsigned char max_forecast_deco[2]; // 2 bytes [minutes]
+ unsigned char max_forecast_deco_bt[2]; // 2 bytes [minutes]
+//64
+ unsigned char max_ceiling[2]; // 2 bytes [*10 = ft]
+ unsigned char max_ceiling_bt[2]; // 2 bytes [minutes]
+ unsigned char unknown16[10]; // 18 bytes
+ unsigned char max_ascent_rate; // 1 byte [ft/min]
+ unsigned char unknown17[3]; // 3 bytes
+ unsigned char max_ascent_rate_bt[2]; // 2 bytes [seconds]
+//84
+ unsigned char unknown18[54]; // 54 bytes
+//138
+ unsigned char end_battery_voltage[2]; // 2 bytes [/256 = v]
+ unsigned char unknown19[8]; // 8 bytes
+ unsigned char min_temp_bt[2]; // 2 bytes [seconds]
+//150
+ unsigned char unknown20[22]; // 22 bytes
+//172
+ unsigned char end_nofly[2]; // 2 bytes [/256 = hours]
+ unsigned char alarm_count[2]; // 2 byte
+ unsigned char actual_deco_time[2]; // 2 bytes [seconds]
+//178
+ unsigned char unknown21[38]; // 38 bytes
+//216
+ unsigned char end_tissue_nsat[20][2]; // 40 bytes [/256 = fraction]
+} __attribute__((packed));
+
+typedef enum cochran_emc_bitfield_config_t {
+ BF_TEMP_DEPENDENT_N2,
+ BF_ASCENT_RATE_BAR_GRAPH,
+ BF_BLEND_2_SWITCHING,
+ BF_ALTITUDE_AS_ONE_ZONE,
+ BF_DECOMPRESSION_TIME_DISPLAY,
+ BF_BLEND_3_SWITCHING,
+ BF_VARIABLE_ASCENT_RATE_ALARM,
+ BF_ASCENT_RATE_RESPONSE,
+ BF_REPETITIVE_DIVE_DEPENDENT_N2,
+ BF_TRAINING_MODE,
+ BF_CONSTANT_MODE_COMPUTATIONS,
+ BF_DISPLAYED_UNITS,
+ BF_AUDIBLE_ALARM,
+ BF_CLOCK,
+ BF_CEILING_DISPLAY_DIV_BY_10,
+ BF_GAS_2_AS_FIRST_GAS,
+ BF_ENABLE_HELIUM_COMPUTATIONS,
+ BF_AUTOMATIC_PO2_FO2_SWITCHING,
+ BF_TOUCH_PROGRAMMING_PO2_FO2_SWITCH,
+} cochran_emc_bitfield_config_t;
+
+
+struct cochran_emc_bitfield_t {
+ cochran_emc_bitfield_config_t config;
+ unsigned char word;
+ unsigned char byte;
+ unsigned char mask;
+ unsigned char shift;
+} cochran_emc_bitfield_t;
+
+static struct cochran_emc_bitfield_t cochran_emc_bits[] = {
+// Word BD
+ { BF_TEMP_DEPENDENT_N2, 0xBD, 0, 0x40, 6 }, // 0=normal, 1=reduced
+ { BF_ASCENT_RATE_BAR_GRAPH, 0xBD, 0, 0x20, 5 }, // 0=fixed, 1=proportional
+ { BF_BLEND_2_SWITCHING, 0xBD, 0, 0x04, 2 }, // 0=dis, 1=ena
+ { BF_ALTITUDE_AS_ONE_ZONE, 0xBD, 0, 0x02, 1}, // 0=off, 1=on
+
+ { BF_DECOMPRESSION_TIME_DISPLAY, 0xBD, 1, 0xC0, 5}, // 111=both, 011=stop, 001=total
+ { BF_BLEND_3_SWITCHING, 0xBD, 1, 0x10, 4 }, // 0=dis, 1=ena
+ { BF_VARIABLE_ASCENT_RATE_ALARM, 0xBD, 1, 0x04, 3}, // 0=off, 1=on
+ { BF_ASCENT_RATE_RESPONSE, 0xBD, 1, 0x07, 0},
+
+//WORD BE
+ { BF_REPETITIVE_DIVE_DEPENDENT_N2, 0xBE, 0, 0x80, 7 }, // 0=off, 1=on
+ { BF_TRAINING_MODE, 0xBE, 0, 0x04, 2 }, // 0=off, 1=on
+ { BF_CONSTANT_MODE_COMPUTATIONS, 0xBE, 0, 0x04, 2 }, // 0=FO2, 1=PO2
+ { BF_DISPLAYED_UNITS, 0xBE, 0, 0x01, 0 }, // 1=metric, 0=imperial
+
+// WORD BF
+ { BF_AUDIBLE_ALARM, 0xBF, 0, 0x40, 6 }, // 0=on, 1=off ***
+ { BF_CLOCK, 0xBF, 0, 0x20, 5 }, // 0=off, 1=on
+ { BF_CEILING_DISPLAY_DIV_BY_10, 0xBF, 0, 0x10, 4 }, // 0=off, 1=on
+ { BF_GAS_2_AS_FIRST_GAS, 0xBF, 0, 0x02, 1 }, // 0=dis, 1=ena
+ { BF_ENABLE_HELIUM_COMPUTATIONS, 0xBF, 0, 0x01, 0 }, // 0=dis, 1=ena
+
+ { BF_AUTOMATIC_PO2_FO2_SWITCHING, 0xBF, 1, 0x04, 2 }, // 0=dis, 1=ena
+ { BF_TOUCH_PROGRAMMING_PO2_FO2_SWITCH, 0xBF, 1, 0x02, 1 }, // 0=dis, 1=ena
+};
+
+struct cochran_emc_config1_t {
+ unsigned char unknown1[209];
+ unsigned short int dive_count;
+ unsigned char unknown2[274];
+ unsigned short int serial_num;
+ unsigned char unknown3[24];
+} __attribute__((packed));
diff --git a/file.c b/file.c
index da5ea830d..6ada903fd 100644
--- a/file.c
+++ b/file.c
@@ -351,13 +351,9 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
/* CSV files */
if (!strcasecmp(fmt, "CSV"))
return 1;
-
-#if ONCE_COCHRAN_IS_SUPPORTED
/* Truly nasty intentionally obfuscated Cochran Anal software */
if (!strcasecmp(fmt, "CAN"))
return try_to_open_cochran(filename, mem);
-#endif
-
/* Cochran export comma-separated-value files */
if (!strcasecmp(fmt, "DPT"))
return try_to_open_csv(filename, mem, CSV_DEPTH);
diff --git a/file.h b/file.h
index 2e4d3ebde..8c5b48bc9 100644
--- a/file.h
+++ b/file.h
@@ -6,9 +6,7 @@ struct memblock {
size_t size;
};
-#if 0
-extern int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error);
-#endif
+extern int try_to_open_cochran(const char *filename, struct memblock *mem);
#ifdef __cplusplus
extern "C" {
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index 0a6112b4f..9aadfd313 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -1225,7 +1225,7 @@ void MainWindow::loadFiles(const QStringList fileNames)
void MainWindow::on_actionImportDiveLog_triggered()
{
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
- tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;"
+ tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db *.can);;"
"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
"Suunto Files(*.sde *.db);;CSV Files(*.csv);;MkVI Files(*.txt);;All Files(*)"));
diff --git a/subsurface.pro b/subsurface.pro
index 724e0215d..53f7f4b6b 100644
--- a/subsurface.pro
+++ b/subsurface.pro
@@ -21,6 +21,8 @@ QMAKE_CLEAN += $$TARGET
VERSION = 4.2
HEADERS = \
+ cochran_cmdr.h \
+ cochran_emc.h \
color.h \
deco.h \
device.h \
@@ -110,6 +112,7 @@ android: HEADERS -= \
qt-ui/printoptions.h
SOURCES = \
+ cochran.c \
deco.c \
device.c \
dive.c \