]> git.tdb.fi Git - ext/subsurface.git/commitdiff
Add some initial cochran CAN file parsing
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 27 Jan 2012 20:43:40 +0000 (12:43 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 27 Jan 2012 20:43:40 +0000 (12:43 -0800)
It's broken, and currently only writes out a debug output file per dive.
I'm not sure I'll ever really be able to decode the mess that is the
Cochran ANalyst stuff, but I have a few test files, along with separate
depth info from a couple of the dives in question, so in case this ever
works I can at least validate it to some degree.

The file format is definitely very intentionally obscured, though.
Annoying.  It's not like the Cochran software is actually all that good
(it's really quite a horribly nasty Windows-only app, I'm told).

Cochran Analyst is very much not the reason why people would buy those
computers.  So Cochran making their computers harder to use with other
software is just stupid.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Makefile
cochran.c [new file with mode: 0644]
file.c
file.h [new file with mode: 0644]

index 265f8fe4e8db691f0b59d04711caddf815c54e6c..20dff4c857f11d5d9286245e9d43c8a5d6b63aa0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -120,7 +120,7 @@ LIBS = $(LIBXML2) $(LIBXSLT) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALI
 
 OBJS = main.o dive.o profile.o info.o equipment.o divelist.o \
        parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o \
-       gtk-gui.o statistics.o file.o $(OSSUPPORT).o $(RESFILE)
+       gtk-gui.o statistics.o file.o cochran.o $(OSSUPPORT).o $(RESFILE)
 
 $(NAME): $(OBJS)
        $(CC) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS)
@@ -152,9 +152,12 @@ install-macosx: $(NAME)
        $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/
        $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXINSTALL)/Contents/Resources/
 
-file.o: file.c dive.h
+file.o: file.c dive.h file.h
        $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) $(ZIP) -c file.c
 
+cochran.o: cochran.c dive.h file.h
+       $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) $(ZIP) -c cochran.c
+
 parse-xml.o: parse-xml.c dive.h
        $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) -c parse-xml.c
 
diff --git a/cochran.c b/cochran.c
new file mode 100644 (file)
index 0000000..907dc76
--- /dev/null
+++ b/cochran.c
@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "dive.h"
+#include "file.h"
+
+/*
+ * 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: "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.
+ *
+ * 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)
+{
+       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;
+}
+
+/*
+ * 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;
+}
+
+static void cochran_debug_write(int dive, const unsigned char *data, unsigned size)
+{
+       char buffer[60];
+       int fd;
+
+       snprintf(buffer, sizeof(buffer), "cochran.%d.out", dive);
+       fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY, 0666);
+       if (fd >= 0) {
+               write(fd, data, size);
+               close(fd);
+       }
+}
+
+static void parse_cochran_dive(int dive, const unsigned char *decode, unsigned mod,
+               const unsigned char *in, unsigned size)
+{
+       char *buf = malloc(size);
+
+       /*
+        * 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:
+        */
+       partial_decode(0x48ff, size, decode, 0, mod, in, size, buf);
+
+       cochran_debug_write(dive, buf, size);
+
+       free(buf);
+}
+
+int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error)
+{
+       unsigned int i;
+       unsigned int mod;
+       unsigned int *offsets, dive1, dive2;
+       unsigned char *decode = mem->buffer + 0x40001;
+
+       if (mem->size < 0x40000)
+               return 0;
+       offsets = 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);
+
+       for (i = 0; i < 65534; i++) {
+               dive1 = offsets[i];
+               dive2 = offsets[i+1];
+               if (dive2 < dive1)
+                       break;
+               if (dive2 > mem->size)
+                       break;
+               parse_cochran_dive(i, decode, mod, mem->buffer + dive1, dive2 - dive1);
+       }
+
+       return 1;
+}
diff --git a/file.c b/file.c
index 2e46f94fd1ac2009a707507e8abdbee98b2bb7ad..aff1d51f2d2c87b470f2b61b23d6c8b945870f73 100644 (file)
--- a/file.c
+++ b/file.c
@@ -6,11 +6,7 @@
 #include <errno.h>
 
 #include "dive.h"
-
-struct memblock {
-       void *buffer;
-       size_t size;
-};
+#include "file.h"
 
 static int readfile(const char *filename, struct memblock *mem)
 {
@@ -104,6 +100,10 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
        if (!strcasecmp(fmt, "SDE"))
                return try_to_open_suunto(filename, mem, error);
 
+       /* Truly nasty intentionally obfuscated Cochran Anal software */
+       if (!strcasecmp(fmt, "CAN"))
+               return try_to_open_cochran(filename, mem, error);
+
        return 0;
 }
 
diff --git a/file.h b/file.h
new file mode 100644 (file)
index 0000000..df7e5ea
--- /dev/null
+++ b/file.h
@@ -0,0 +1,11 @@
+#ifndef FILE_H
+#define FILE_H
+
+struct memblock {
+       void *buffer;
+       size_t size;
+};
+
+extern int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error);
+
+#endif