From 6ca1cdb717c55f8a4b4567285e9b44a8822504c9 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 23 Jun 2020 00:55:08 +0000 Subject: [PATCH] work on spo files --- Makefile | 4 +- dump-float.c | 32 +++- dump-float2.c | 11 ++ dump-spo.c | 94 ++++++++-- dump-spo2.c | 467 ++++++++++++++++++++++++++++++++++++++++++++++ spo-notes | 508 +++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 1088 insertions(+), 28 deletions(-) create mode 100644 dump-float2.c create mode 100644 dump-spo2.c diff --git a/Makefile b/Makefile index c9a1c7d551..5e208d79bc 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,11 @@ parse-xml.o: CFLAGS := $(shell pkg-config --cflags libxml-2.0) $(base_cflags) parse-xml: LDFLAGS := $(shell pkg-config --libs libxml-2.0) $(LDFLAGS) dump2.o: CFLAGS := $(base_cflags) -Wno-unused dump-spo.o: CFLAGS := $(base_cflags) -Wno-unused +dump-spo2.o: CFLAGS := $(base_cflags) -Wno-unused -all: dump dump2 parse-xml dump-spo +all: dump dump2 parse-xml dump-spo dump-spo2 dump-spo: dump-spo.o u8-mbtouc.o +dump-spo2: dump-spo2.o u8-mbtouc.o dump: dump.o u8-mbtouc.o dump2: dump2.o u8-mbtouc.o parse-xml: parse-xml.o diff --git a/dump-float.c b/dump-float.c index 84e4115e19..21aa391d06 100644 --- a/dump-float.c +++ b/dump-float.c @@ -1,15 +1,33 @@ #include #include +#include + +void +print_double (const uint8_t *b) +{ + double d; + memcpy (&d, b, 8); + printf ("%f\n", d); +} + +void +print_float (const uint8_t *b) +{ + float d; + memcpy (&d, b, 4); + printf ("%f\n", d); +} int main (void) { - union - { - uint8_t b[8]; - double d; - } - x = { .b = { 0x18, 0x00, 0x00, 0x1c, 0xf0, 0xff, 0xff, 0x5f } }; - printf ("%f\n", x.d); + uint8_t b[] = { 0x1f, 0x05, 0, 0, 0xa2, 3, 0, 0, 0x64, 0, 0, 0 }; + int n = sizeof b; + for (int i = 0; i <= n - 8; i++) + print_double (b + i); +/* + for (int i = 0; i <= n - 4; i++) + print_float (b + i); +*/ return 0; } diff --git a/dump-float2.c b/dump-float2.c new file mode 100644 index 0000000000..0d27404a43 --- /dev/null +++ b/dump-float2.c @@ -0,0 +1,11 @@ +#include +#include +#include + +int +main (void) +{ + double d = 100; + fwrite (&d, 1, sizeof d, stdout); + return 0; +} diff --git a/dump-spo.c b/dump-spo.c index 1ca9f0a03f..fe35a0bb4f 100644 --- a/dump-spo.c +++ b/dump-spo.c @@ -576,7 +576,7 @@ dump_cell(void) } static void -dump_category (int level, uint8_t *catstart, bool *have_catstart) +dump_category (int level, uint8_t *delim, int *delim_len) { int cat_index = get_u32(); assert (cat_index < 256); @@ -619,25 +619,30 @@ dump_category (int level, uint8_t *catstart, bool *have_catstart) printf (" (group with %d children)", n_children); else printf (" (category #%d)", cat_index); + printf ("\n"); - printf (" %02x %02x %02x %02x %02x %02x\n", - data[pos], data[pos + 1], data[pos + 2], - data[pos + 3], data[pos + 4], data[pos + 5]); - if (!*have_catstart) + int dlen = 0; + for (int i = 0; i < 2; i++) { - *have_catstart = true; - memcpy(catstart, &data[pos], 6); + if (data[pos + dlen] == 0xff) + dlen += 7; + else + dlen += 3; + } + if (!*delim_len) + { + *delim_len = dlen; + memcpy(delim, &data[pos], dlen); } - pos += 6; + pos += dlen; for (int i = 0; i < n_children; i++) - dump_category (level + 1, catstart, have_catstart); + dump_category (level + 1, delim, delim_len); } static void dump_PMModelItemInfo(int ndims) { - return; #if 0 if (data[pos + 9] && data[pos + 9] != 0xff)//count_zeros (&data[pos + 9]) < 4) return; @@ -645,22 +650,22 @@ dump_PMModelItemInfo(int ndims) match_byte_assert (0); - uint8_t catstart[6]; - bool have_catstart = false; - dump_category (0, catstart, &have_catstart); - assert(have_catstart); + uint8_t delim[14]; + int delim_len = 0; + dump_category (0, delim, &delim_len); + assert(delim_len); for (int i = 1; i < ndims; i++) { for (int j = 0; ; j++, pos++) { - assert (j <= 1000); - if (!memcmp(&data[pos], catstart, 6)) + assert (pos + j + delim_len < n); + if (!memcmp(&data[pos], delim, delim_len)) break; } - pos += 6; + pos += delim_len; - dump_category (0, catstart, &have_catstart); + dump_category (0, delim, &delim_len); } } @@ -720,6 +725,44 @@ main(int argc, char *argv[]) setvbuf (stdout, NULL, _IOLBF, 0); + pos = 0x39b; + unsigned int rtflen = get_u32(); + pos += rtflen - 1; /* Almost past SPSS syntax */ + assert(pos < n); + pos += 0x45 + data[pos + 0x44]; /* Just past the string */ + pos += 0x1a + data[pos + 0x19]; /* Just past the string again */ + pos += 0x66 + data[pos + 0x65]; /* Just past the third string */ + pos += 0x4e; + rtflen = get_u32(); + pos += rtflen - 1; /* Almost past 2nd RTF */ + pos += 0x64 + data[pos + 0x63]; /* Just past the fourth string */ + /* as-number: */ + if (data[pos + 0x114] == 0xff) + pos += 0x117 + data[pos + 0x115] + 256 * data[pos + 0x116]; + else + pos += 0x115 + data[pos + 0x114]; + pos += 0x18 + data[pos + 0x17]; /* Just past "" or dataset name. */ + pos += 0x18 + data[pos + 0x17]; /* Just past "" or dataset name. */ + pos += count_zeros(&data[pos]); /* Skip optional zeros. */ + pos += 0x18 + data[pos + 0x17]; /* Just past "" or dataset name. */ + pos += 0x3e + data[pos + 0x3d]; /* Skip past "100" etc. */ + pos += count_zeros(&data[pos]); /* Skip optional zeros. */ + pos += 0x18 + data[pos + 0x17]; /* Just past "User-defined...". */ + pos += 0x18 + data[pos + 0x17]; /* Just past "Statistics are based... */ + if (data[pos + 0x19] == 0xff) + pos += 0x1c + data[pos + 0x1a] + 256 * data[pos + 0x1b]; + else + pos += 0x1a + data[pos + 0x19]; + pos += 0x61 + data[pos + 0x60]; /* Just past "Cluster_Notes". */ + pos += 0x992 + data[pos + 0x991]; /* Just past "Cluster". */ + pos += 0x4e; + rtflen = get_u32(); + pos += rtflen - 1; /* Almost past RTF with filesystem path */ + pos += 0x45 + data[pos + 0x44]; /* Just past "Statistics" */ + pos += 0x1a + data[pos + 0x19]; /* Just past "Cluster". */ + fwrite (&data[pos], 1, n - pos, stdout); + exit (0); + int sum = 0; #if 0 @@ -1048,7 +1091,20 @@ main(int argc, char *argv[]) continue; } - + if (data[pos] == 0 && data[pos + 1] == 0xff + && (!data[pos + 2] || data[pos + 2] == 0xff) && data[pos + 3] == 0xff + && data[pos + 4] == 0) + { + pos++; + if (prev_end != pos) + { + if (print_offsets) + printf ("%04x ", prev_end); + hex_dump (stdout, prev_end, pos - prev_end); + } + prev_end = pos; + continue; + } if (!is_ascii(data[pos])) diff --git a/dump-spo2.c b/dump-spo2.c new file mode 100644 index 0000000000..3367163275 --- /dev/null +++ b/dump-spo2.c @@ -0,0 +1,467 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "u8-mbtouc.h" + +static const char *filename; +static uint8_t *data; +static size_t n; + +int version; + +unsigned int pos; + +#define XSTR(x) #x +#define STR(x) XSTR(x) +#define WHERE __FILE__":" STR(__LINE__) + +static uint8_t +get_byte(void) +{ + return data[pos++]; +} + +static unsigned int +get_u32(void) +{ + uint32_t x; + memcpy(&x, &data[pos], 4); + pos += 4; + return x; +} + +static unsigned long long int +get_u64(void) +{ + uint64_t x; + memcpy(&x, &data[pos], 8); + pos += 8; + return x; +} + +static unsigned int +get_be32(void) +{ + uint32_t x; + x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]; + pos += 4; + return x; +} + +static unsigned int +get_u16(void) +{ + uint16_t x; + memcpy(&x, &data[pos], 2); + pos += 2; + return x; +} + +static double +get_double(void) +{ + double x; + memcpy(&x, &data[pos], 8); + pos += 8; + return x; +} + +static double __attribute__((unused)) +get_float(void) +{ + float x; + memcpy(&x, &data[pos], 4); + pos += 4; + return x; +} + +static bool +match_u32(uint32_t x) +{ + if (get_u32() == x) + return true; + pos -= 4; + return false; +} + +bool +match_u16(uint16_t x) +{ + if (get_u16() == x) + return true; + pos -= 2; + return false; +} + +static void +match_u32_assert(uint32_t x, const char *where) +{ + unsigned int y = get_u32(); + if (x != y) + { + fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y); + exit(1); + } +} +#define match_u32_assert(x) match_u32_assert(x, WHERE) + +static void +match_u16_assert(uint16_t x, const char *where) +{ + unsigned int y = get_u16(); + if (x != y) + { + fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u\n", where, pos - 2, x, y); + exit(1); + } +} +#define match_u16_assert(x) match_u16_assert(x, WHERE) + +static bool __attribute__((unused)) +match_u64(uint64_t x) +{ + if (get_u64() == x) + return true; + pos -= 8; + return false; +} + +static void __attribute__((unused)) +match_u64_assert(uint64_t x, const char *where) +{ + unsigned long long int y = get_u64(); + if (x != y) + { + fprintf(stderr, "%s: 0x%x: expected u64:%lu, got u64:%llu\n", where, pos - 8, x, y); + exit(1); + } +} +#define match_u64_assert(x) match_u64_assert(x, WHERE) + +static bool __attribute__((unused)) +match_be32(uint32_t x) +{ + if (get_be32() == x) + return true; + pos -= 4; + return false; +} + +static void +match_be32_assert(uint32_t x, const char *where) +{ + unsigned int y = get_be32(); + if (x != y) + { + fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y); + exit(1); + } +} +#define match_be32_assert(x) match_be32_assert(x, WHERE) + +static bool +match_byte(uint8_t b) +{ + if (pos < n && data[pos] == b) + { + pos++; + return true; + } + else + return false; +} + +static void +match_byte_assert(uint8_t b, const char *where) +{ + if (!match_byte(b)) + { + fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]); + exit(1); + } +} +#define match_byte_assert(b) match_byte_assert(b, WHERE) + +static bool +match_bytes(int start, const int *bytes, size_t n_bytes) +{ + for (size_t i = 0; i < n_bytes; i++) + if (bytes[i] >= 0 && data[start + i] != bytes[i]) + return false; + return true; +} + +static char * +xmemdup0(const void *p, size_t n) +{ + char *s = malloc(n + 1); + memcpy(s, p, n); + s[n] = 0; + return s; +} + +static bool +get_bool(void) +{ + if (match_byte(0)) + return false; + match_byte_assert(1); + return true; +} + +static bool __attribute__((unused)) +is_ascii(uint8_t p) +{ + return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t'; +} + +static int +count_zeros(const uint8_t *p) +{ + size_t n = 0; + while (p[n] == 0) + n++; + return n; +} + +static bool __attribute__((unused)) +all_utf8(const char *p_, size_t len) +{ + const uint8_t *p = (const uint8_t *) p_; + for (size_t ofs = 0, mblen; ofs < len; ofs += mblen) + { + ucs4_t uc; + + mblen = u8_mbtouc (&uc, p + ofs, len - ofs); + if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd) + return false; + } + return true; +} + +static char * +get_string1(void) +{ + int len = data[pos++]; + char *s = xmemdup0(&data[pos], len); + pos += len; + return s; +} + +static char * +get_string2(void) +{ + int len = data[pos] + data[pos + 1] * 256; + char *s = xmemdup0(&data[pos + 2], len); + pos += 2 + len; + return s; +} + +static char * +get_string(const char *where) +{ + if (1 + /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/ + /*&& all_ascii(&data[pos + 4], data[pos])*/) + { + int len = data[pos] + data[pos + 1] * 256; + char *s = malloc(len + 1); + + memcpy(s, &data[pos + 4], len); + s[len] = 0; + pos += 4 + len; + return s; + } + else + { + fprintf(stderr, "%s: 0x%x: expected string\n", where, pos); + exit(1); + } +} +#define get_string() get_string(WHERE) + +static char * +get_string_be(const char *where) +{ + if (1 + /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/ + /*&& all_ascii(&data[pos + 4], data[pos])*/) + { + int len = data[pos + 2] * 256 + data[pos + 3]; + char *s = malloc(len + 1); + + memcpy(s, &data[pos + 4], len); + s[len] = 0; + pos += 4 + len; + return s; + } + else + { + fprintf(stderr, "%s: 0x%x: expected string\n", where, pos); + exit(1); + } +} +#define get_string_be() get_string_be(WHERE) + +static int +get_end(void) +{ + int len = get_u32(); + return pos + len; +} + +static void __attribute__((unused)) +hex_dump(FILE *stream, int ofs, int n) +{ + int n_ascii = 0; + for (int i = 0; i < n; i++) + { + int c = data[ofs + i]; + n_ascii += is_ascii(c); + fprintf(stream, " %02x", c); + } + if (n_ascii >= 3) + { + putc(' ', stream); + for (int i = 0; i < n; i++) + { + int c = data[ofs + i]; + putc(c >= 32 && c < 127 ? c : '.', stream); + } + } + putc('\n', stream); +} + +static void __attribute__((unused)) +char_dump(FILE *stream, int ofs, int n) +{ + for (int i = 0; i < n; i++) + { + int c = data[ofs + i]; + putc(c >= 32 && c < 127 ? c : '.', stream); + } + putc('\n', stream); +} + + +static int +compare_int(const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + return *a < *b ? -1 : *a > *b; +} + + +static const char * +format_name (int format, char *buf) +{ + switch (format) + { + case 1: return "A"; + case 2: return "AHEX"; + case 3: return "COMMA"; + case 4: return "DOLLAR"; + case 5: case 40: return "F"; + case 6: return "IB"; + case 7: return "PIBHEX"; + case 8: return "P"; + case 9: return "PIB"; + case 10: return "PK"; + case 11: return "RB"; + case 12: return "RBHEX"; + case 15: return "Z"; + case 16: return "N"; + case 17: return "E"; + case 20: return "DATE"; + case 21: return "TIME"; + case 22: return "DATETIME"; + case 23: return "ADATE"; + case 24: return "JDATE"; + case 25: return "DTIME"; + case 26: return "WKDAY"; + case 27: return "MONTH"; + case 28: return "MOYR"; + case 29: return "QYR"; + case 30: return "WKYR"; + case 31: return "PCT"; + case 32: return "DOT"; + case 33: return "CCA"; + case 34: return "CCB"; + case 35: return "CCC"; + case 36: return "CCD"; + case 37: return "CCE"; + case 38: return "EDATE"; + case 39: return "SDATE"; + default: sprintf(buf, "(%d)", format); return buf; + } +} + + +int +main(int argc, char *argv[]) +{ + bool print_offsets = false; + for (;;) + { + int c = getopt (argc, argv, "o"); + if (c == -1) + break; + + switch (c) + { + case 'o': + print_offsets = true; + break; + + case '?': + exit (-1); + } + } + if (argc - optind != 1) + { + fprintf (stderr, "usage: %s FILE.bin", argv[0]); + exit (1); + } + + const char *filename = argv[optind]; + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno)); + exit (1); + } + + struct stat s; + if (fstat(fd, &s)) + { + perror("fstat"); + exit(1); + } + n = s.st_size; + data = malloc(n); + if (!data) + { + perror("malloc"); + exit(1); + } + if (read(fd, data, n) != n) + { + perror("read"); + exit(1); + } + close(fd); + + setvbuf (stdout, NULL, _IOLBF, 0); + + return 0; +} diff --git a/spo-notes b/spo-notes index 92038d722c..30132d6f5c 100644 --- a/spo-notes +++ b/spo-notes @@ -25,6 +25,13 @@ and the DspString case: 01 02 28 (00|05) 00 (00|01) often followed by a string +The first byte might be a version number. "Save As" or "Save" has the effect: + 00 -> 00 (tried 1 out of 3) + 01 -> 04 (tried 3 out of 3) + 02 -> 04 (tried 1 out of many: deska, regress) + 03 -> 04 (tried 1 out of many: 3d67bb) + 04 -> 04 (tried 1 out of many: detailer2) + Charset numbers come from the list for \fcharset in the RTF specification: http://latex2rtf.sourceforge.net/rtfspec_6.html#rtfspec_10 The charset @@ -188,7 +195,7 @@ ffff 0000 "IndexedCollection" 01 00 ffff 0000 "DspCell" - 00 03 80 00 00 00 00 00 00 00 00 00 00 + 00 03 80 00*10 ffff 0000 "DspNumber" @@ -557,3 +564,502 @@ that if a node is collapsed it *increases* by 2000 then decreases by 421:-83360 (-2000) 04 00 00 00 04 00 00 422:cell F40.2 "LogAC" + +ZMAW_zaj3: + +rec:PVTextStyle +.. 02 .. 02 +.. c8 .. LM .. RM .. TM .. BM .. .. .. 01 .. yy yy yy yy ww ww .. .. xx xx xx xx .. .. .. .. zz zz zz zz ii uu ss cc cc cc cc 22 "Arial" FF FF FF .. .. .. a7 80 .. 01 .. .. .. .. .. BB BB BB .. SS .. a9 80 .. 01 .. .. +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. 01 .. .. +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. .. .. .. +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. 01 .. 02 +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. 02 .. 03 +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. 01 .. .. +.. c8 .. 78 .. a0 .. 14 .. 14 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. a7 80 .. 01 .. .. .. .. .. ff ff ff .. .. .. a9 80 .. 01 .. .. +.. c8 .. 68 01 68 01 28 .. 28 .. .. .. 01 .. f3 ff ff ff .. .. .. .. .. .. .. .. .. .. .. .. 90 01 .. .. .. .. .. ee 00 00 00 22 "Arial" .. .. .. .. .. .. .. .. 01 .. .. .. .. .. .. .. 01 .. 01 .. .. .. .. .. .. .. 01 +.. 01 .. .. .. .. .. .. .. 01 .. 01 .. .. .. .. .. .. .. 01 .. .. .. .. .. .. .. .. .. 01 .. .. .. .. .. .. .. 01 .. 01 .. .. .. .. .. .. .. 01 .. + +yy: font size, f2ffffff or f3ffffff is normalish, f8ffffff is tiny, +f0ffffff is big +ww: something to do with font size or stretch. 01 is normal, 02+ gets weird +xx: rotation angle +zz: 400 is normal weight, 700+ is bold +ii: 1+ is italic +uu: 1+ is underline +ss: 1+ is strikethrough +cc: RTF charset number, e.g. 0 for ANSI, 238 for Polish +FF: RGB foreground +BB: RGB background +SS: shade fraction from 0=unshaded to 0xa=opaque +LT, RM, TM, BM: margins, 20=1 point + +Some confusion on separation, BB and later seem to correspond to the next area + +Areas: +0. title +1. layers +2. corner +3. row heads +4. column heads +5. data +6. caption +7. footnotes + + rr gg bb 00 00 00 ss 00 01 + ff 00 ff 00 00 00 01 00 01 00 + ff 00 ff 00 00 00 01 00 01 00 + ff 00 ff 00 00 00 01 00 01 00 + ff 00 ff 00 00 00 01 00 00 00 + 00 00 00 00 00 00 01 00 + ff 00 ff 00 00 00 01 00 01 00 + ff 00 ff 00 00 00 01 00 + +rr gg bb: stroke color +ss: style, 0=thin, 1=normal, 2=thick, 3=very thick, otherwise weird + +Borders: +- horizontal category columns +- vertical category columns +... +- left inner frame +- right inner frame +- top inner frame +- bottom inner frame +- left data area +- top data area + + +.. +01 .. ff .. ff .. .. .. .. .. +.. .. .. .. .. .. 03 80 .. +01 .. ff .. ff .. .. .. .. .. +01 .. ff .. ff .. .. .. .. .. +01 .. ff .. ff .. .. .. .. .. +01 .. ff .. ff .. .. .. .. .. + +.. +01 .. .. .. .. .. .. .. .. .. +.. .. .. .. .. .. 03 80 .. +01 .. .. .. .. .. .. .. .. .. .. .. +01 .. .. .. .. .. .. .. .. .. .. .. + +.. +01 .. 80 80 80 .. .. .. 01 .. +01 .. 80 80 80 .. .. .. 01 .. +01 .. 80 80 80 .. .. .. .. .. +01 .. 80 80 80 .. .. .. .. .. +ba 80 .. +01 .. 80 80 80 .. .. .. 01 .. +01 .. 80 80 80 .. .. .. 01 .. +01 .. 80 80 80 .. .. .. .. .. +01 .. 80 80 80 .. .. .. .. .. + +class {00000319-0000-0000-c000-000000000046}: Enhanced Metafile + For these, dropping the first 4 bytes of Embedding*/CONTENTS file + yields an EMF file that the printemf program can dissect. + +class {62078ae0-e4fe-11cd-838d-0000c0f17248}: + harder to pin down what's in these + +view-spo can Edit|copy notes and tables for pasting into other applications + +rigger: contains working graph! + +correspondence: + 1/01 80 ? "NavRoot" + 3/03 80 -> "DspSimpleText" + 5/05 80 -> "DspString" 01 02 28 (00|05) 00 01 b"string" + 7/07 80 -> "NavTreeViewItem" + 9/09 80 -> "NavLog" +11/0b 80 ? "NavHead" +13/0d 80 ? "NavTitle" +15/0f 80 ??? +17/11 80 ??? +19/13 80 ??? +21/15 80 ??? +24 18 80 -> "NavNote" +29 1d 80 -> "PTPivotController" +31 1f 80 -> "PTPivotView" +33/21 80 -> "PMPivotModel" +35/23 80 -> "NDimensional__DspCell" +37/25 80 -> "IndexedCollection" +39/27 80 -> "DspCell" +41/29 80 ? "DspNumber" + /50 80 -> "PMPivotItemTree" + /52 80 -> "AbstractTreeBranch" + /54 80 -> "PMModelItemInfo" + + +When the top-level "Output" node is selected for save: + 04 i0 + "SPSS Output Document" + i1 63 + ffff 0000 "NavRoot" 02 00*32 + ffff 0000 "DspSimpleText" 00*10 + ffff 0000 "DspString" 01 02 28 00 00 01 b"Output" + ffff 0000 "NavTreeViewItem" 00 i0 02 00 01 00*9 i1 +00c0 (i0 | i24) (i24 | i-40) (i0 | i40 | i-40) (i40 | i-34) +00d0 (i1048 | some other 3-hex-digit number | i0) +00d4 (i0 | some 3 to 4-hex-digit number | i-40) +00d8 00*5 +00dd (i8500 i11000 | i11000 i8500) +00e5 (s1 | s9) (s1 | s7 | s15) (s1 | s2) +00eb i1270*4 +00fb s0 i240 s?? s1 b"(Continued)" 01 01 00*3 byte 00*3 +0116 w"{\rtf... pagetitle}" +01fd 01 01 00*3 +0202 w"{\rtf... page number}" +02e9 00 short s2 + +(This is a valid spot for the file to contain 00000000 then eof) + +02ee ffff 0000 "NavLog" 02 i0*2 i24 +0307 (i0 | i-40) (i691 | i987) (2-3 digit negative int) i1 +0317 i0 +0318 03 80 00*10 +0324 05 80 01 02 28 05 00 01 b"Log" +0333 07 80 00 i2 0a 00 01 00*9 i1 00*2 01 01 (f3|f4) ff ff ff 00*4 +0357 00*8 90 01 00*9 (22|31) +036b 32-bytes of null-padded font name, e.g. "Courier New". + Sometimes garbage after the first null. +038b (i80 | i132) 00*8 i1 +038f 00*8 i1 +039b w"{\rtf ... SPSS syntax...}" + +Almost past SPSS syntax (only the } included): +0000 '}' 00 +0002 ffff 0000 "NavHead" 02 +0010 00*24 i1 i0 +0030 03 80 00 00 00 00 00 00 00 00 00 00 05 80 01 02 +0040 28 05 00 01 +0044 b"Cluster|Crosstabs|..." + +Just past the string: +0000 07 80 00 00 00 00 00 0f 00 01 00 00 00 00 00 00 +0010 00 00 00 01 00 00 00 00 00 +0019 b"Cluster|Crosstabs|..." + +Just past the string again: +0000 00*3 +0003 ffff 0000 "NavTitle" +0011 65 02 00 00 00 00 00 00 00 00 18 00 00 00 byte byte +0020 ff ff 00 04 00 00 byte byte +0028 ff ff 02 00 00 00 01 00 00 00 03 80 00*10 +003e 05 80 01 02 28 00 00 01 b"Title" +004c 07 80 00 08 +0050 00 00 00 14 00 01 00*9 01 +0060 00*5 +0065 b"Cluster|Crosstabs|..." + +Just past the third string: +0000 01 01 ed ff ff ff 00 00 00 00 00 00 00 00 00 00 +0010 00 00 bc 02 00 00 00 00 00 00 00 00 00 22 +001e 32-bytes of buggy zero-padded string "Arial" +003e 50 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 +004e w"{\rtf...}" + +Almost past the RTF (only } included): +0000 '}' 00 +0002 ffff 0000 "NavNote" +0010 02 00 00 00 00 00 00 00 00 18 00 00 00 (i0 | i-40) +0020 int int i2 i1 +0030 03 80 00*10 +003c 05 80 01 02 28 05 00 01 b"Notes" +004a 07 80 00 i7 19 00 01 00 00*2 01 00*5 01 00*5 +0063 b"Cluster|Crosstabs|..." + +Just past the fourth string: +0000 01 + +0001 ffff 0000 "PTPivotController" +0018 02 +0019 byte*8 # weird ASCIIish garbage +0021 i100*4 + +0031 ffff 0000 "PVPivotView" +0042 05 00 00 00 00 + +0047 ffff 0000 "PMPivotModel" +0059 03 + +005a ffff 0000 "NDimensional__DspCell" +0075 00 01 00 00 00 + +007a ffff 0000 "IndexedCollection" +0091 00 byte 00 00 00 01 00 + +0098 ffff 0000 "DspCell" +00a6 00 +00a7 03 80 00*10 + +Fork: cell contains number ("as-number"): +00b2 ffff 0000 "DspNumber" +00c1 01 00 14 16 # 00 14 16 is DATETIME20.0 +00c5 80 02 +00c7 double # seconds since 1582... +00cf b"16-APR-2007 15:20:00" # or whatever +00e4 27 +00e5 80 00 03 80 00 00 00 00 00 00 00 00 00 00 05 +00f4 80 01 02 28 05 00 01 01 20 + # Some files have extra 00 00 here + # but we're only considering as-number-nozeros +00fd 27 80 00 +0100 03 80 00*10 +010e 05 80 01 02 28 05 00 01 +0114 # Either b"filename" with a length < 255 + # or ff "filename" with a length >= 255 + +as-number-nozeros, after filename: +0000 27 80 00 +0003 03 80 00*10 05 +0010 80 01 02 28 05 00 01 b" or DataSet1 or whatever" + +as-number-nozeros, after dataset name: +0000 27 80 00 +0003 03 80 00*10 05 +0010 80 01 02 28 05 00 01 b"" + +# Some files have extra 00*8 here, we skip it: +0000 27 80 00 +0003 03 80 00*5 (00|10) 00*4 05 +0010 80 01 02 28 05 00 01 b" or DATASET..." + +as-number-nozeros-majority, after dataset name: +0000 27 80 00 03 80 00*10 05 80 01 02 28 05 00 01 b"" +001e 27 80 00 03 80 00*10 2a 80 01 00 28 05 80 02 100.0 b"100" + # Followed by 6 optional zeros + +as-number-nozeros-majority, after optional zeros: +0000 27 80 00 03 80 00*10 05 80 01 02 28 05 00 01 b"User-defined missing values..." + ^ sixth 00 may be 10 instead + +as-number-nozeros-majority, after "User-defined missing values...": +0000 27 80 00 03 80 00*10 (05|2a) 80 01 02 28 05 00 01 b"Statistics are based on..." + +as-number-nozeros-majority, after "Statistics are based on...": +0000 00 00 +0002 27 80 00 03 80 00*10 05 80 01 02 28 05 00 01 b"syntax fragment" + ^ sixth 00 may be 10 instead + +as-number-nozeros-majority, after syntax fragment: +0000 27 80 00 03 80 00*10 2a 80 01 02 0d 15 80 02 double b"0:00:00.02" +selecting for the ones that include Processor Time instead of RAM: +002a 27 80 00 03 80 00*10 2a 80 01 02 0d 15 80 02 double b"0:00:00.03" +0054 01 00 b"Notes" 00*4 +0060 b"Cluster_Notes" + +processor-time, after previous string: +0000 00 01 00 00 00 01 00 + +0007 ffff 0000 "PMPivotItemTree" 00 +001d ffff 0000 "AbstractTreeBranch" 00 +0036 ffff 0000 "PMModelItemInfo" + 00*9 byte*3 0e 00 03 80 00*10 +0065 05 80 01 02 28 05 00 01 b"Contents" i7 + 52 80 00 + 54 80 00 00*9 07 00 74 0e 00 03 80 00*10 +0099 05 80 01 02 28 05 00 01 b"Output Created" i0 + 52 80 00 + 54 80 00 01 00*7 08 00 74 0e 00 03 80 00*10 +00d3 05 80 01 02 28 05 00 01 b"Comments" i0 +00e8 52 80 00 + 54 80 00*9 09 00 74 0e 00 03 80 00*10 + 05 80 01 02 28 05 00 01 b"Input" i6 + 52 80 00 + 54 80 00 02 00*7 0a 00 74 0e 00 03 80 00*10 +0138 05 80 01 02 28 05 00 01 b"Data" i0 + 52 80 00 + 54 80 00 03 00*12 03 80 00*10 + 05 80 01 02 28 05 00 01 b"Active Dataset" i0 +0183 52 80 00 + 54 80 00 04 00*7 0e 00 74 0e 00 03 80 00*9 +01a2 05 80 01 02 28 05 00 01 b"Filter" i0 +01b5 52 80 00 + 54 80 00 05 00*7 11 00 74 0e 00 03 80 00*10 +01d4 05 80 01 02 28 05 00 01 b"Weight" i0 +01e7 52 80 00 + 54 80 00 06 00 00 +01f0 00 00 00 00 00 12 00 74 0e 00 03 80 00 00 00 00 +0200 00 00 00 00 00 00 05 80 01 02 28 05 00 01 b"Split File" +0219 00 00 00 00 52 80 00 +0220 54 80 00 07 00 00 00 00 00 00 00 13 00 74 0e 00 +0230 03 80 00 00 00 00 00 00 00 00 00 00 05 80 01 02 +0240 28 05 00 01 b"N of Rows in Working Data File" +0263 00 00 00 00 52 80 00 54 80 00 00 00 00 +0270 00 00 00 00 00 byte*2 74 0e 00 03 80 00 00 00 00 +0280 00 00 00 00 00 00 05 80 01 02 28 05 00 01 +028e b"Missing Value Handling" +02a5 02 00 00 00 52 80 00 54 80 00 08 +02b0 00 00 00 00 00 00 00 byte*2 74 0e 00 03 80 00 00 +02c0 00 00 00 00 00 00 00 00 05 80 01 02 28 05 00 01 +02d0 b"Definition of Missing" +02e6 00 00 00 00 52 80 00 54 80 00 +02f0 09 00 00 00 00 00 00 00 byte*2 74 0e 00 03 80 00 +0300 00 00 00 00 00 00 00 00 00 05 80 01 02 28 05 00 +0310 01 b"Cases Used" 00*4 +0320 52 80 00 54 80 00 0a 00 00 00 00 00 00 00 17 00 +0330 74 0e 00 03 80 00 00 00 00 00 00 00 00 00 00 05 +0340 80 01 02 28 05 00 01 b"Weight Handling" +0357 00 00 00 00 52 80 00 54 80 +0360 00 0b 00 00 00 00 00 00 00 18 00 74 0e 00 03 80 +0370 00 00 00 00 00 00 00 00 00 00 05 80 01 02 28 05 +0380 00 01 b"Syntax" 00 00 00 00 52 80 00 +0390 54 80 00 00 00 00 00 00 00 00 00 byte*2 74 0e 00 +03a0 03 80 00 00 00 00 00 00 00 00 00 00 05 80 01 02 +03b0 28 05 00 01 b"Resources" 02 00 +03c0 00 00 52 80 00 54 80 00 0c 00 00 00 00 00 00 00 +03d0 1b 00 74 0e 00 03 80 00 00 00 00 00 00 00 00 00 +03e0 00 05 80 01 02 28 05 00 01 b"Elapsed Time" +03f6 00 00 00 00 52 80 00 54 80 00 +0400 0d 00 00 00 00 00 00 00 00 00 00 00 00 03 80 00 +0410 00 00 00 00 00 00 00 00 00 05 80 01 02 28 05 00 +0420 01 b"Processor Time" +0430 00 00 00 00 00 00 00 00 00 00 0e 00 00 00 01 00 +0440 58 00 5c 00 64 00 68 00 6c 00 70 00 74 00 78 00 +0450 80 00 84 00 88 00 8c 00 94 00 98 00 00 0e 00 00 +0460 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 +0470 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 07 +0480 00 00 00 08 00 00 00 09 00 00 00 0a 00 00 00 0b +0490 00 00 00 0c 00 00 00 0d 00 00 00 00 00 00 00 00 +04a0 01 00 00 11 00 00 00 00 00 04 00 00 00 e1 00 00 +04b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +04c0 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 +04d0 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 +04e0 00 01 00 ff ff 00 00 b"PVViewDimension" +04f8 00 51 00 00 00 00 00 00 +0500 00 00 00 00 0e 00 00 00 00 00 69 00 00 00 54 00 +0510 00 00 4b 00 00 00 63 00 00 00 4b 00 00 00 4b 00 +0520 00 00 4b 00 00 00 7d 00 00 00 87 00 00 00 5c 00 +0530 00 00 72 00 00 00 4b 00 00 00 63 00 00 00 6f 00 +0540 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 +0550 00 87 00 00 00 00 03 80 00 00 00 00 00 00 00 00 +0560 00 00 05 80 01 02 28 05 00 01 b"Notes" +0570 00 03 80 00 00 00 00 00 00 00 00 00 00 05 80 01 +0580 02 28 00 00 01 00 00 00 00 00 00 00 00 00 03 80 +0590 00 00 00 00 00 00 00 00 00 00 05 80 01 02 28 00 +05a0 00 01 00 + +05a3 ffff 0000 "PTTableLook" +05b4 02 02 00 00 00 00 00 00 00 36 00 00 +05c0 00 12 00 00 00 + +05c5 ffff 0000 "PVSeparatorStyle" +05db 00 01 00 00 00 +05e0 00 00 00 00 00 00 00 00 00 00 00 00 a6 80 00 01 |................| +05f0 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 |................| +0600 00 00 00 01 00 00 00 00 00 00 00 00 00 01 00 00 |................| +0610 00 00 00 00 00 00 00 + +0607 ffff 0000 "PVCellStyle" +0628 00 01 00 00 00 00 00 ff + +0630 ffff 0000 00 + +0637 ffff 0000 "PVTextStyle" +0646 00 02 00 02 00 c8 00 78 00 a0 +0650 00 14 00 78 00 00 00 01 00 f3 ff ff ff 00 00 00 +0660 00 00 00 00 00 00 00 00 00 bc 02 00 00 00 00 00 +0670 00 00 00 00 22 b"Arial" 00 00 00 00 00 +0680 00 a9 80 00 01 00 00 00 00 00 ff ff ff 00 00 00 +0690 ab 80 00 01 00 00 00 c8 00 78 00 a0 00 14 00 14 +06a0 00 00 00 01 00 f3 ff ff ff 00 00 00 00 00 00 00 +06b0 00 00 00 00 00 90 01 00 00 00 00 00 00 00 00 00 +06c0 22 b"Arial" 00 00 00 00 00 00 a9 80 00 +06d0 01 00 00 00 00 00 ff ff ff 00 00 00 ab 80 00 01 +06e0 00 00 00 c8 00 78 00 a0 00 14 00 14 00 00 00 01 +06f0 00 f3 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 +0700 00 90 01 00 00 00 00 00 00 00 00 00 22 b"Arial" +0713 00 00 00 00 00 00 a9 80 00 01 00 00 00 +0720 00 00 ff ff ff 00 00 00 ab 80 00 00 00 00 00 c8 +0730 00 78 00 a0 00 14 00 14 00 00 00 01 00 f3 ff ff +0740 ff 00 00 00 00 00 00 00 00 00 00 00 00 90 01 00 +0750 00 00 00 00 00 00 00 00 22 b"Arial" 00 +0760 00 00 00 00 00 a9 80 00 01 00 00 00 00 00 ff ff +0770 ff 00 00 00 ab 80 00 01 00 02 00 c8 00 78 00 a0 +0780 00 14 00 14 00 00 00 01 00 f3 ff ff ff 00 00 00 +0790 00 00 00 00 00 00 00 00 00 90 01 00 00 00 00 00 +07a0 00 00 00 00 22 b"Arial" 00 00 00 00 00 +07b0 00 a9 80 00 01 00 00 00 00 00 ff ff ff 00 00 00 +07c0 ab 80 00 02 00 03 00 c8 00 78 00 a0 00 14 00 14 +07d0 00 00 00 01 00 f3 ff ff ff 00 00 00 00 00 00 00 +07e0 00 00 00 00 00 90 01 00 00 00 00 00 00 00 00 00 +07f0 22 b"Arial" 00 00 00 00 00 00 a9 80 00 +0800 01 00 00 00 00 00 ff ff ff 00 00 00 ab 80 00 01 +0810 00 00 00 c8 00 78 00 a0 00 14 00 14 00 00 00 01 +0820 00 f3 ff ff ff 00 00 00 00 00 00 00 00 00 00 00 +0830 00 90 01 00 00 00 00 00 00 00 00 00 22 b"Arial" +0843 00 00 00 00 00 00 a9 80 00 01 00 00 00 +0850 00 00 ff ff ff 00 00 00 ab 80 00 01 00 00 00 c8 +0860 00 68 01 68 01 28 00 28 00 00 00 01 00 f3 ff ff +0870 ff 00 00 00 00 00 00 00 00 00 00 00 00 90 01 00 +0880 00 00 00 00 00 00 00 00 22 b"Arial" 00 +0890 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 +08a0 00 01 00 00 00 00 00 00 00 01 00 01 00 00 00 00 +08b0 00 00 00 01 00 01 00 00 00 00 00 00 00 01 00 00 +08c0 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 +08d0 00 01 00 00 00 00 00 00 00 01 00 b"(cont.)" +08e3 24 00 00 00 48 00 00 00 24 00 00 00 78 +08f0 00 00 00 00 00 00 00 00 00 00 00 + +08fb ffff 0000 "PVPrintManager" 02 +0910 1e 00 02 00 00 00 03 00 00 00 01 00 00 00 00 00 +0920 00 00 00 00 00 00 00 + +0927 ffff 0000 "NavText" +0934 02 00 00 00 00 00 00 00 00 18 00 00 +0940 00 byte*2 ff ff byte*2 00 00 byte*2 ff ff 02 00 00 +0950 00 01 00 00 00 03 80 00 00 00 00 00 00 00 00 00 +0960 00 05 80 01 02 28 05 00 01 b"Active Dataset" +0978 07 80 00 10 00 00 00 be +0980 00 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 +0990 00 b"Cluster etc." 07 43 6c 75 73 74 65 72 01 01 f3 ff ff ff 00 +0990 00 0b 46 72 65 71 75 65 6e 63 69 65 73 01 01 f3 + +Just past "Cluster": +0000 01 01 (f3|f4) ff ff ff 00 00 00 00 00 00 00 00 00 00 +0010 00 00 90 01 00 00 00 00 00 00 00 00 00 (22|31) +001e 32-bytes of null-padded font name, e.g. "Courier New". + Sometimes garbage after the first null. +003e byte 00*11 w"{\rtf... giving filesystem path}" + +Almost past RTF (just trailing }): +0000 '}' ffff 0000 "NavPivot" 02 int int i24 int +0020 int int i2 i1 +0030 03 80 00*10 05 80 01 02 +0040 28 05 00 01 b"Statistics etc." + +Just past "Statistics": +0000 07 80 00 0c 00 00 00 c3 00 01 00 00 00 00 00 00 +0010 00 00 00 01 00 00 00 00 00 b"Cluster etc." + +Just past "Cluster", using boot/Contents as exemplary: +0000 01 +0001 1d 80 02 i0*2 i100*4 # 1d 80 corresponds to "PTPivotController" +001c 1f 80 05 00*4 # 1f 80 corresponds to "PTPivotView" +0023 21 80 03 # 21 80 corresponds to "PMPivotModel" +0026 23 80 00 (02|03) 00 00 00 # 23 80 corresponds to "NDimensional__DspCell" +002d 25 80 00 i100 01 00 # 25 80 corresponds to "IndexedCollection" +0036 25 80 00 i100 01 00 +003f 27 80 00 03 80 00*10 2a 80 01 F40.0 80 02 100.0 b"100" +0062 27 80 00 03 80 00*10 2a 80 01 F40.0 80 02 100.0 b"100" + ...repeats... +0f03 25 80 00 6c 00 00 00 01 00 +0f0c 27 80 00 03 80 00*10 2a 80 01 F40.0 80 02 0.0 b"0" + ...repeats... +1cf8 25 80 00 6c 00 00 00 01 00 +1d01 27 80 00 03 80 00*10 2a 80 01 F40.8 1 + +Notes +Output Created 16-JAN-2008 02:45:55 +Comments +Input Data \\Ugly\amead\consulting\center\Dell\Exams\RE\analyses\Blended15\pilotdata-2008-01-15.csv + Active Dataset raw1 + Filter + Weight + Split File + N of Rows in Working Data File 366 +Missing Value Handling Definition of Missing User-defined missing values are treated as missing. + Cases Used Statistics are based on all cases with valid data. +Syntax FREQUENCIES VARIABLES=exam_id exam_name restarts /ORDER= ANALYSIS . +Resources Elapsed Time 0:00:00.02 + Processor Time 0:00:00.03 -- 2.30.2