start moving beyond PMModelItemInfo
[pspp] / dump-spo.c
1 #include <assert.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <float.h>
5 #include <stdbool.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/stat.h>
11 #include <time.h>
12 #include <unistd.h>
13 #include "u8-mbtouc.h"
14
15 static const char *filename;
16 static uint8_t *data;
17 static size_t n;
18
19 int version;
20
21 unsigned int pos;
22
23 #define XSTR(x) #x
24 #define STR(x) XSTR(x)
25 #define WHERE __FILE__":" STR(__LINE__)
26
27 static uint8_t
28 get_byte(void)
29 {
30   return data[pos++];
31 }
32
33 static unsigned int
34 get_u32(void)
35 {
36   uint32_t x;
37   memcpy(&x, &data[pos], 4);
38   pos += 4;
39   return x;
40 }
41
42 static unsigned long long int
43 get_u64(void)
44 {
45   uint64_t x;
46   memcpy(&x, &data[pos], 8);
47   pos += 8;
48   return x;
49 }
50
51 static unsigned int
52 get_be32(void)
53 {
54   uint32_t x;
55   x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
56   pos += 4;
57   return x;
58 }
59
60 static unsigned int
61 get_u16(void)
62 {
63   uint16_t x;
64   memcpy(&x, &data[pos], 2);
65   pos += 2;
66   return x;
67 }
68
69 static double
70 get_double(void)
71 {
72   double x;
73   memcpy(&x, &data[pos], 8);
74   pos += 8;
75   return x;
76 }
77
78 static double __attribute__((unused))
79 get_float(void)
80 {
81   float x;
82   memcpy(&x, &data[pos], 4);
83   pos += 4;
84   return x;
85 }
86
87 static bool
88 match_u32(uint32_t x)
89 {
90   if (get_u32() == x)
91     return true;
92   pos -= 4;
93   return false;
94 }
95
96 bool
97 match_u16(uint16_t x)
98 {
99   if (get_u16() == x)
100     return true;
101   pos -= 2;
102   return false;
103 }
104
105 static void
106 match_u32_assert(uint32_t x, const char *where)
107 {
108   unsigned int y = get_u32();
109   if (x != y)
110     {
111       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
112       exit(1);
113     }
114 }
115 #define match_u32_assert(x) match_u32_assert(x, WHERE)
116
117 static void
118 match_u16_assert(uint16_t x, const char *where)
119 {
120   unsigned int y = get_u16();
121   if (x != y)
122     {
123       fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u\n", where, pos - 2, x, y);
124       exit(1);
125     }
126 }
127 #define match_u16_assert(x) match_u16_assert(x, WHERE)
128
129 static bool __attribute__((unused))
130 match_u64(uint64_t x)
131 {
132   if (get_u64() == x)
133     return true;
134   pos -= 8;
135   return false;
136 }
137
138 static void __attribute__((unused))
139 match_u64_assert(uint64_t x, const char *where)
140 {
141   unsigned long long int y = get_u64();
142   if (x != y)
143     {
144       fprintf(stderr, "%s: 0x%x: expected u64:%lu, got u64:%llu\n", where, pos - 8, x, y);
145       exit(1);
146     }
147 }
148 #define match_u64_assert(x) match_u64_assert(x, WHERE)
149
150 static bool __attribute__((unused))
151 match_be32(uint32_t x)
152 {
153   if (get_be32() == x)
154     return true;
155   pos -= 4;
156   return false;
157 }
158
159 static void
160 match_be32_assert(uint32_t x, const char *where)
161 {
162   unsigned int y = get_be32();
163   if (x != y)
164     {
165       fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y);
166       exit(1);
167     }
168 }
169 #define match_be32_assert(x) match_be32_assert(x, WHERE)
170
171 static bool
172 match_byte(uint8_t b)
173 {
174   if (pos < n && data[pos] == b)
175     {
176       pos++;
177       return true;
178     }
179   else
180     return false;
181 }
182
183 static void
184 match_byte_assert(uint8_t b, const char *where)
185 {
186   if (!match_byte(b))
187     {
188       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
189       exit(1);
190     }
191 }
192 #define match_byte_assert(b) match_byte_assert(b, WHERE)
193
194 static bool
195 match_bytes(int start, const int *bytes, size_t n_bytes)
196 {
197   for (size_t i = 0; i < n_bytes; i++)
198     if (bytes[i] >= 0 && data[start + i] != bytes[i])
199       return false;
200   return true;
201 }
202
203 static char *
204 xmemdup0(const void *p, size_t n)
205 {
206   char *s = malloc(n + 1);
207   memcpy(s, p, n);
208   s[n] = 0;
209   return s;
210 }
211
212 static bool
213 get_bool(void)
214 {
215   if (match_byte(0))
216     return false;
217   match_byte_assert(1);
218   return true;
219 }
220
221 static bool __attribute__((unused))
222 is_ascii(uint8_t p)
223 {
224   return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t';
225 }
226
227 static int
228 count_zeros(const uint8_t *p)
229 {
230   size_t n = 0;
231   while (p[n] == 0)
232     n++;
233   return n;
234 }
235
236 static bool __attribute__((unused))
237 all_utf8(const char *p_, size_t len)
238 {
239   const uint8_t *p = (const uint8_t *) p_;
240   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
241     {
242       ucs4_t uc;
243
244       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
245       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
246         return false;
247     }
248   return true;
249 }
250
251 static char *
252 get_string1(void)
253 {
254   int len = data[pos++];
255   char *s = xmemdup0(&data[pos], len);
256   pos += len;
257   return s;
258 }
259
260 static char *
261 get_string2(void)
262 {
263   int len = data[pos] + data[pos + 1] * 256;
264   char *s = xmemdup0(&data[pos + 2], len);
265   pos += 2 + len;
266   return s;
267 }
268
269 static char *
270 get_string(const char *where)
271 {
272   if (1
273       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
274       /*&& all_ascii(&data[pos + 4], data[pos])*/)
275     {
276       int len = data[pos] + data[pos + 1] * 256;
277       char *s = malloc(len + 1);
278
279       memcpy(s, &data[pos + 4], len);
280       s[len] = 0;
281       pos += 4 + len;
282       return s;
283     }
284   else
285     {
286       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
287       exit(1);
288     }
289 }
290 #define get_string() get_string(WHERE)
291
292 static char *
293 get_string_be(const char *where)
294 {
295   if (1
296       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
297       /*&& all_ascii(&data[pos + 4], data[pos])*/)
298     {
299       int len = data[pos + 2] * 256 + data[pos + 3];
300       char *s = malloc(len + 1);
301
302       memcpy(s, &data[pos + 4], len);
303       s[len] = 0;
304       pos += 4 + len;
305       return s;
306     }
307   else
308     {
309       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
310       exit(1);
311     }
312 }
313 #define get_string_be() get_string_be(WHERE)
314
315 static int
316 get_end(void)
317 {
318   int len = get_u32();
319   return pos + len;
320 }
321
322 static void __attribute__((unused))
323 hex_dump(FILE *stream, int ofs, int n)
324 {
325   int n_ascii = 0;
326   for (int i = 0; i < n; i++)
327     {
328       int c = data[ofs + i];
329       n_ascii += is_ascii(c);
330       fprintf(stream, " %02x", c);
331     }
332   if (n_ascii >= 3)
333     {
334       putc(' ', stream);
335       for (int i = 0; i < n; i++)
336         {
337           int c = data[ofs + i];
338           putc(c >= 32 && c < 127 ? c : '.', stream);
339         }
340     }
341   putc('\n', stream);
342 }
343
344 static void __attribute__((unused))
345 char_dump(FILE *stream, int ofs, int n)
346 {
347   for (int i = 0; i < n; i++)
348     {
349       int c = data[ofs + i];
350       putc(c >= 32 && c < 127 ? c : '.', stream);
351     }
352   putc('\n', stream);
353 }
354
355
356 static int
357 compare_int(const void *a_, const void *b_)
358 {
359   const int *a = a_;
360   const int *b = b_;
361   return *a < *b ? -1 : *a > *b;
362 }
363
364
365 static const char *
366 format_name (int format, char *buf)
367 {
368   switch (format)
369     {
370     case 1: return "A";
371     case 2: return "AHEX";
372     case 3: return "COMMA";
373     case 4: return "DOLLAR";
374     case 5: case 40: return "F";
375     case 6: return "IB";
376     case 7: return "PIBHEX";
377     case 8: return "P";
378     case 9: return "PIB";
379     case 10: return "PK";
380     case 11: return "RB";
381     case 12: return "RBHEX";
382     case 15: return "Z";
383     case 16: return "N";
384     case 17: return "E";
385     case 20: return "DATE";
386     case 21: return "TIME";
387     case 22: return "DATETIME";
388     case 23: return "ADATE";
389     case 24: return "JDATE";
390     case 25: return "DTIME";
391     case 26: return "WKDAY";
392     case 27: return "MONTH";
393     case 28: return "MOYR";
394     case 29: return "QYR";
395     case 30: return "WKYR";
396     case 31: return "PCT";
397     case 32: return "DOT";
398     case 33: return "CCA";
399     case 34: return "CCB";
400     case 35: return "CCC";
401     case 36: return "CCD";
402     case 37: return "CCE";
403     case 38: return "EDATE";
404     case 39: return "SDATE";
405     default: sprintf(buf, "(%d)", format); return buf;
406     }
407 }
408
409 static void
410 dump_DspNumber(void)
411 {
412   match_byte_assert(1);
413   int d = get_byte();
414   int w = get_byte();
415   int fmt = get_byte();
416   char buf[64];
417   printf ("%s%d.%d ", format_name (fmt, buf), w, d);
418
419   match_byte_assert(0x80);
420   match_byte_assert(2);
421   printf ("%f ", get_double ());
422   printf ("\"%s\"\n", get_string1 ());
423
424   for (;;)
425     {
426       if (data[pos] == 0xff)
427         {
428           printf ("\nff exit");
429           break;
430         }
431
432       if (data[pos] == 0x80 && data[pos + 1] == 1)
433         {
434           pos += 2;
435           int d = get_byte();
436           int w = get_byte();
437           int fmt = get_byte();
438           char buf[64];
439           printf ("\n%% %s%d.%d\n", format_name (fmt, buf), w, d);
440         }
441       else if (data[pos] == 0x80 && data[pos + 1] == 2)
442         {
443           pos += 2;
444           printf ("\n%f ", get_double ());
445           printf ("'%s'\n", get_string1 ());
446         }
447       else if (data[pos] == 0x80 && data[pos + 1] == 0 && data[pos + 2] == 3)
448         pos += 3;
449       else if (data[pos] == 0x80 && count_zeros(&data[pos + 1]) == 10)
450         pos += 11;
451       else if (data[pos] == 0x1 && data[pos + 1] == 0xff)
452         {
453           pos += 2;
454           printf ("\n\"%s\"\n", get_string2 ());
455         }
456       else if (data[pos] == 0x1 && data[pos + 1])
457         {
458           pos += 1;
459           printf ("\n\"%s\"\n", get_string1 ());
460         }
461       else
462         printf ("%02x ", get_byte());
463     }
464 }
465
466 static void
467 dump_cell(void)
468 {
469   static const int cell_prefix[] = {
470     0x00, 0x03, 0x80,
471     0x00, 0x00, 0x00, 0x00, 0x00, -1 /* 00 or 10 */, 0x00, 0x00, 0x00, 0x00,
472
473     /*13  14    15  16  17  18  19 */
474     -1, 0x80, 0x01, -1, -1, -1, -1,
475   };
476   size_t cell_prefix_len = sizeof cell_prefix / sizeof *cell_prefix;
477   if (!match_bytes(pos, cell_prefix, cell_prefix_len))
478     {
479       printf ("match failed at %x\n", pos);
480       return;
481     }
482
483   char buf[64];
484   printf ("cell %s%d.%d ",
485           format_name (data[pos + 18], buf),
486           data[pos + 17],
487           data[pos + 16]);
488
489   int len = cell_prefix_len;
490   if (data[pos + 19] == 0)
491     {
492       assert (data[pos + 13] == 5);
493       if (data[pos + 20] == 0)
494         {
495           int count = (data[pos + 22]);
496           printf ("%d %d \"%.*s\"",
497                   data[pos + 21], data[pos + 22],
498                   count, &data[pos + 23]);
499           len = 23 + count;
500         }
501       else if (data[pos + 20] == 1
502                && data[pos + 21] == 0xff
503                && data[pos + 22] == 0xff)
504         {
505           int count = 255;
506           printf ("%d \"%.*s\"", count, data[pos + 23],
507                   &data[pos + 24]);
508           len = 23 + count;
509         }
510       else if (data[pos + 20] == 1 && data[pos + 21] == 255)
511         {
512           int count = data[pos + 22] + (data[pos + 23] << 8);
513           printf ("\"%.*s\"",
514                   count, &data[pos + 24]);
515           len = 24 + count;
516         }
517       else if (data[pos + 20] == 1)
518         {
519           int count = (data[pos + 21]);
520           printf ("\"%.*s\"",
521                   count, &data[pos + 22]);
522           len = 22 + count;
523         }
524       else
525         assert (false);
526     }
527   else if (data[pos + 19] == 128 && data[pos + 20] == 2)
528     {
529       /* pos + 13 is usually 22...53, and it's 3 more than the
530          " xx 80" separator between cells  */
531       printf ("xxx%x ", data[pos + 13]);
532       double d = *(double *) &data[pos + 21];
533       len = 29;
534       const union
535         {
536           uint8_t b[8];
537           double d;
538         }
539       sysmis = {.b = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}};
540       if (d == sysmis.d)
541         printf ("sysmis");
542       else
543         printf ("%f", d);
544
545       if (data[pos + 29] < 0xff
546           && all_utf8((char *) &data[pos + 30], data[pos + 29]))
547         {
548           printf (" \"%.*s\"", (int) data[pos + 29],
549                   &data[pos + 30]);
550           len += data[pos + 29] + 1;
551         }
552       else
553         assert (false);
554     }
555   else if (data[pos + 19] == 128 && data[pos + 20] == 1 &&
556            data[pos + 21] == 0)
557     {
558       if (data[pos + 23] < 0xff
559           && all_utf8((char *) &data[pos + 24], data[pos + 23]))
560         {
561           printf (" \"%.*s\"", (int) data[pos + 23],
562                   &data[pos + 24]);
563           len = 24 + data[pos + 23];
564         }
565       else
566         assert (false);
567     }
568   else
569     {
570       printf ("xxx%d %d %d %d",
571               data[pos + 19], data[pos + 20],
572               data[pos + 21], data[pos + 22]);
573       assert(false);
574     }
575   pos += len;
576 }
577
578 static void
579 dump_category (int level, uint8_t *delim, int *delim_len)
580 {
581   int cat_index = get_u32();
582   assert (cat_index < 256);
583
584   if (!match_u32 (0))
585     match_u32_assert (1);
586
587   get_u16();
588
589   if (!match_u16(0xe74) && !match_u16(0xffff))
590     match_u16_assert(0);
591
592   for (int i = 0; i < level; i++)
593     printf ("  ");
594
595   for (int i = 0; ; i++, pos++)
596     if (data[pos] == 5 && data[pos + 1] == 0x80)
597       break;
598     else if (i >= 100)
599       assert(false);
600
601   match_byte_assert (5);
602   match_byte_assert (0x80);
603   if (match_byte(2))
604     get_double ();
605   else
606     {
607       match_byte_assert (1);
608       match_byte_assert (2);
609       match_byte_assert (0x28);
610       match_byte_assert (5);
611       match_byte_assert (0);
612       match_byte_assert (1);
613     }
614   printf (" \"%s\"", get_string1());
615
616   int n_children = get_u32();
617   assert (n_children < 256);
618   if (n_children)
619     printf (" (group with %d children)", n_children);
620   else
621     printf (" (category #%d)", cat_index);
622   printf ("\n");
623
624   int dlen = 0;
625   for (int i = 0; i < 2; i++)
626     {
627       if (data[pos + dlen] == 0xff)
628         dlen += 7;
629       else
630         dlen += 3;
631     }
632   if (!*delim_len)
633     {
634       *delim_len = dlen;
635       memcpy(delim, &data[pos], dlen);
636     }
637   pos += dlen;
638
639   for (int i = 0; i < n_children; i++)
640     dump_category (level + 1, delim, delim_len);
641 }
642
643 static void
644 dump_PMModelItemInfo(int ndims)
645 {
646 #if 0
647   if (data[pos + 9] && data[pos + 9] != 0xff)//count_zeros (&data[pos + 9]) < 4)
648     return;
649 #endif
650   
651   match_byte_assert (0);
652
653   uint8_t delim[14];
654   int delim_len = 0;
655   dump_category (0, delim, &delim_len);
656   assert(delim_len);
657
658   for (int i = 1; i < ndims; i++)
659     {
660       for (int j = 0; ; j++, pos++)
661         {
662           assert (pos + j + delim_len < n);
663           if (!memcmp(&data[pos], delim, delim_len))
664             break;
665         }
666       pos += delim_len;
667
668       dump_category (0, delim, &delim_len);
669     }
670 }
671
672 int
673 main(int argc, char *argv[])
674 {
675   bool print_offsets = false;
676   for (;;)
677     {
678       int c = getopt (argc, argv, "o");
679       if (c == -1)
680         break;
681
682       switch (c)
683         {
684         case 'o':
685           print_offsets = true;
686           break;
687
688         case '?':
689           exit (-1);
690         }
691     }
692   if (argc - optind != 1)
693     {
694       fprintf (stderr, "usage: %s FILE.bin", argv[0]);
695       exit (1);
696     }
697
698   const char *filename = argv[optind];
699   int fd = open(filename, O_RDONLY);
700   if (fd < 0)
701     {
702       fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
703       exit (1);
704     }
705
706   struct stat s;
707   if (fstat(fd, &s))
708     {
709       perror("fstat");
710       exit(1);
711     }
712   n = s.st_size;
713   data = malloc(n);
714   if (!data)
715     {
716       perror("malloc");
717       exit(1);
718     }
719   if (read(fd, data, n) != n)
720     {
721       perror("read");
722       exit(1);
723     }
724   close(fd);
725
726   setvbuf (stdout, NULL, _IOLBF, 0);
727
728   pos = 0x39b;
729   unsigned int rtflen = get_u32();
730   pos += rtflen - 1;            /* Almost past SPSS syntax */
731   assert(pos < n);
732   pos += 0x45 + data[pos + 0x44]; /* Just past the string */
733   pos += 0x1a + data[pos + 0x19]; /* Just past the string again */
734   pos += 0x66 + data[pos + 0x65]; /* Just past the third string */
735   pos += 0x4e;
736   rtflen = get_u32();
737   pos += rtflen - 1;            /* Almost past 2nd RTF */
738   pos += 0x64 + data[pos + 0x63]; /* Just past the fourth string */
739   /* as-number: */
740   if (data[pos + 0x114] == 0xff)
741     pos += 0x117 + data[pos + 0x115] + 256 * data[pos + 0x116];
742   else
743     pos += 0x115 + data[pos + 0x114];
744   pos += 0x18 + data[pos + 0x17]; /* Just past "<none>" or dataset name. */
745   pos += 0x18 + data[pos + 0x17]; /* Just past "<none>" or dataset name. */
746   pos += count_zeros(&data[pos]); /* Skip optional zeros. */
747   pos += 0x18 + data[pos + 0x17]; /* Just past "<none>" or dataset name. */
748   pos += 0x3e + data[pos + 0x3d]; /* Skip past "100" etc. */
749   pos += count_zeros(&data[pos]); /* Skip optional zeros. */
750   pos += 0x18 + data[pos + 0x17]; /* Just past "User-defined...". */
751   pos += 0x18 + data[pos + 0x17]; /* Just past "Statistics are based... */
752   if (data[pos + 0x19] == 0xff)
753     pos += 0x1c + data[pos + 0x1a] + 256 * data[pos + 0x1b];
754   else
755     pos += 0x1a + data[pos + 0x19];
756   pos += 0x61 + data[pos + 0x60]; /* Just past "Cluster_Notes". */
757   pos += 0x992 + data[pos + 0x991]; /* Just past "Cluster". */
758   pos += 0x4e;
759   rtflen = get_u32();
760   pos += rtflen - 1;            /* Almost past RTF with filesystem path */
761   pos += 0x45 + data[pos + 0x44]; /* Just past "Statistics" */
762   pos += 0x1a + data[pos + 0x19]; /* Just past "Cluster". */
763   fwrite (&data[pos], 1, n - pos, stdout);
764   exit (0);
765
766   int sum = 0;
767   
768 #if 0
769   unsigned int prev_end = 0;
770   for (pos = 0; pos + 50 < n; pos++)
771     {
772       if (data[pos + 0] == 0xff &&
773           data[pos + 1] == 0xff &&
774           data[pos + 2] == 0 &&
775           data[pos + 3] == 0)
776         {
777           int len = data[pos + 4] + (data[pos + 5] << 8);
778           if (len < 3 || pos + len + 6 >= n || !all_utf8 ((char *) &data[pos + 6], len))
779             continue;
780
781           printf ("+%04x %04x...%04x: %-25.*s\n",
782                   pos - prev_end, pos, pos + 6 + len,
783                   len < 50 ? (int) len : 50, &data[pos + 6]);
784           prev_end = pos + 6 + len;
785         }
786     }
787 #endif
788 #if 0
789   for (pos = 0; pos + 50 < n; pos++)
790     {
791       if (data[pos + 0] == 'L' &&
792           data[pos + 1] == 'o' &&
793           data[pos + 2] == 'g' &&
794           !all_utf8((char *) &data[pos + 3], 1) &&
795           data[pos - 1] != 'v')
796         {
797           if (print_offsets)
798             printf ("%04x: ", pos);
799           unsigned int p = pos;
800           while (all_utf8 ((char *) &data[p], 1))
801             p--;
802           hex_dump (stdout, p - 28, 38);
803         }
804     }
805 #endif
806   unsigned int prev_end = 0;
807   char *title = "";
808   int ndims = 0;
809   for (pos = 2; pos + 50 < n; )
810     {
811       static const int cell_prefix[] = {
812         0x00, 0x03,
813         0x80, 0x00, 0x00, 0x00, 0x00, 0x00, -1 /* 00 or 10 */, 0x00, 0x00, 0x00, 0x00, -1,
814
815         /*14    15  16  17  18  19 */
816         0x80, 0x01, -1, -1, -1, -1,
817       };
818       size_t cell_prefix_len = sizeof cell_prefix / sizeof *cell_prefix;
819       if (match_bytes(pos, cell_prefix, cell_prefix_len))
820         {
821           if (prev_end != pos)
822             {
823               if (print_offsets)
824                 printf ("%04x ", prev_end);
825               hex_dump (stdout, prev_end, pos - prev_end);
826             }
827
828           char buf[64];
829           printf ("cell %s%d.%d ",
830                   format_name (data[pos + 18], buf),
831                   data[pos + 17],
832                   data[pos + 16]);
833
834           int len = cell_prefix_len;
835           if (data[pos + 19] == 0)
836             {
837               assert (data[pos + 13] == 5);
838               if (data[pos + 20] == 0)
839                 {
840                   int count = (data[pos + 22]);
841                   printf ("%d %d \"%.*s\"\n",
842                           data[pos + 21], data[pos + 22],
843                           count, &data[pos + 23]);
844                   len = 23 + count;
845                 }
846               else if (data[pos + 20] == 1
847                        && data[pos + 21] == 0xff
848                        && data[pos + 22] == 0xff)
849                 {
850                   int count = 255;
851                   printf ("%d \"%.*s\"\n", count, data[pos + 23],
852                           &data[pos + 24]);
853                   len = 23 + count;
854                 }
855               else if (data[pos + 20] == 1 && data[pos + 21] == 255)
856                 {
857                   int count = data[pos + 22] + (data[pos + 23] << 8);
858                   printf ("\"%.*s\"\n",
859                           count, &data[pos + 24]);
860                   len = 24 + count;
861                 }
862               else if (data[pos + 20] == 1)
863                 {
864                   int count = (data[pos + 21]);
865                   printf ("\"%.*s\"\n",
866                           count, &data[pos + 22]);
867                   len = 22 + count;
868                 }
869               else
870                 assert (false);
871             }
872           else if (data[pos + 19] == 128 && data[pos + 20] == 2)
873             {
874               /* pos + 13 is usually 22...53, and it's 3 more than the
875                  " xx 80" separator between cells  */
876               printf ("xxx%x ", data[pos + 13]);
877               double d = *(double *) &data[pos + 21];
878               len = 29;
879               const union
880                 {
881                   uint8_t b[8];
882                   double d;
883                 }
884               sysmis = {.b = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}};
885               if (d == sysmis.d)
886                 printf ("sysmis");
887               else
888                 printf ("%f", d);
889
890               if (data[pos + 29] < 0xff
891                   && all_utf8((char *) &data[pos + 30], data[pos + 29]))
892                 {
893                   printf (" \"%.*s\"", (int) data[pos + 29],
894                           &data[pos + 30]);
895                   len += data[pos + 29] + 1;
896                 }
897               else
898                 assert (false);
899
900               putchar ('\n');
901             }
902           else if (data[pos + 19] == 128 && data[pos + 20] == 1 &&
903                    data[pos + 21] == 0)
904             {
905               if (data[pos + 23] < 0xff
906                   && all_utf8((char *) &data[pos + 24], data[pos + 23]))
907                 {
908                   printf (" \"%.*s\"\n", (int) data[pos + 23],
909                           &data[pos + 24]);
910                   len = 24 + data[pos + 23];
911                 }
912               else
913                 assert (false);
914             }
915           else
916             {
917               printf ("xxx%d %d %d %d\n",
918                       data[pos + 19], data[pos + 20],
919                       data[pos + 21], data[pos + 22]);
920               assert(false);
921             }
922           pos += len;
923           prev_end = pos;
924           continue;
925         }
926
927       static const int record_prefix[] = {
928         0xff, 0xff, 0x00, 0x00,
929       };
930       size_t record_prefix_len = sizeof record_prefix / sizeof *record_prefix;
931       if (match_bytes(pos, record_prefix, record_prefix_len))
932         {
933           int len = record_prefix_len;
934           int slen = data[pos + 4] + (data[pos + 5] << 8);
935           if (slen >= 2 && slen < 256 && all_utf8((char *) &data[pos + 6], slen))
936             {
937               if (prev_end != pos)
938                 {
939                   if (print_offsets)
940                     printf ("%04x ", prev_end);
941                   hex_dump (stdout, prev_end, pos - prev_end);
942                 }
943
944               putchar ('\n');
945
946               printf ("rec:%-20.*s ", slen, &data[pos + 6]);
947               len = slen + 6;
948               title = xmemdup0(&data[pos + 6], slen);
949               sum += data[pos+len];
950
951               pos += len;
952
953               if (!strcmp(title, "DspNumber"))
954                 dump_DspNumber();
955               else if (!strcmp(title, "PMModelItemInfo"))
956                 {
957                   assert(ndims);
958                   dump_PMModelItemInfo(ndims);
959                 }
960               else if (!strcmp(title, "NDimensional__DspCell"))
961                 {
962                   match_byte_assert(0);
963                   ndims = get_u32();
964                   assert(ndims < 10);
965                 }
966               prev_end = pos;
967               continue;
968             }
969         }
970
971       static const int number_prefix[] = {
972         0x80, 0x02
973       };
974       size_t number_prefix_len = sizeof number_prefix / sizeof *number_prefix;
975       if (match_bytes(pos, number_prefix, number_prefix_len))
976         {
977           if (prev_end != pos)
978             {
979               if (print_offsets)
980                 printf ("%04x ", prev_end);
981               hex_dump (stdout, prev_end, pos - prev_end);
982             }
983           prev_end = pos;
984
985           double d = *(double *) &data[pos + number_prefix_len];
986           printf ("float %f\n", d);
987
988           pos += 10;
989           prev_end = pos;
990           continue;
991         }
992
993       if (!memcmp (&data[pos + 4], "{\\rtf", 5))
994         {
995           int len = data[pos] + (data[pos + 1] << 8) + (data[pos + 2] << 16)
996             + (data[pos + 3] << 24);
997           if (len < n - pos - 4)
998             {
999               if (prev_end != pos)
1000                 {
1001                   if (print_offsets)
1002                     printf ("%04x ", prev_end);
1003                   hex_dump (stdout, prev_end, pos - prev_end);
1004                 }
1005               prev_end = pos;
1006
1007               printf ("rtf\n");
1008               pos += 4 + len;
1009               prev_end = pos;
1010               continue;
1011             }
1012         }
1013
1014       if (data[pos] && data[pos + 1] && data[pos + 2] >= 0xfe
1015           && data[pos + 3] == 0xff && data[pos + 4] && data[pos + 4] != 0xff)
1016         {
1017           if (prev_end != pos)
1018             {
1019               if (print_offsets)
1020                 printf ("%04x ", prev_end);
1021               hex_dump (stdout, prev_end, pos - prev_end);
1022             }
1023           prev_end = pos;
1024
1025           static int prev_num;
1026           int32_t num = data[pos] + (data[pos + 1] << 8)
1027             + (data[pos + 2] << 16) + (data[pos + 3] << 24);
1028           printf ("%d (%+d) ", num, num - prev_num);
1029           prev_num = num;
1030           pos += 4;
1031           prev_end = pos;
1032           continue;
1033         }
1034
1035       static const int font_prefix[] = 
1036         {
1037           0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x41, 0x72, 0x69, 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1038         };
1039       size_t font_prefix_len = sizeof font_prefix / sizeof *font_prefix;
1040       if (match_bytes(pos, font_prefix, font_prefix_len))
1041         {
1042           if (prev_end != pos)
1043             {
1044               if (print_offsets)
1045                 printf ("%04x ", prev_end);
1046               hex_dump (stdout, prev_end, pos - prev_end);
1047             }
1048           prev_end = pos;
1049
1050           printf ("font\n");
1051
1052           pos += font_prefix_len;
1053           prev_end = pos;
1054           continue;
1055         }
1056
1057       static const int string_prefix[] = {
1058         0x05, 0x80, 0x01, 0x02, 0x28, 0x05, 0x00, 0x01
1059       };
1060       size_t string_prefix_len = sizeof string_prefix / sizeof *string_prefix;
1061       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] != 255)
1062         {
1063           if (prev_end != pos)
1064             {
1065               if (print_offsets)
1066                 printf ("%04x ", prev_end);
1067               hex_dump (stdout, prev_end, pos - prev_end);
1068             }
1069           prev_end = pos;
1070
1071           int len = data[pos + 8];
1072           printf ("string %.*s\n", len, &data[pos + 9]);
1073           pos += 8 + len;
1074           prev_end = pos;
1075           continue;
1076         }
1077       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] == 255)
1078         {
1079           if (prev_end != pos)
1080             {
1081               if (print_offsets)
1082                 printf ("%04x ", prev_end);
1083               hex_dump (stdout, prev_end, pos - prev_end);
1084             }
1085           prev_end = pos;
1086
1087           int len = data[pos + 9] + (data[pos + 10] << 8);
1088           printf ("\nlongstring %.*s\n", len, &data[pos + 11]);
1089           pos += 11 + len;
1090           prev_end = pos;
1091           continue;
1092         }
1093
1094       if (data[pos] == 0 && data[pos + 1] == 0xff
1095           && (!data[pos + 2] || data[pos + 2] == 0xff) && data[pos + 3] == 0xff
1096           && data[pos + 4] == 0)
1097         {
1098           pos++;
1099           if (prev_end != pos)
1100             {
1101               if (print_offsets)
1102                 printf ("%04x ", prev_end);
1103               hex_dump (stdout, prev_end, pos - prev_end);
1104             }
1105           prev_end = pos;
1106           continue;
1107         }
1108
1109
1110       if (!is_ascii(data[pos]))
1111         {
1112           pos++;
1113           continue;
1114         }
1115
1116       unsigned int start = pos;
1117       unsigned int end = pos + 1;
1118       while (is_ascii(data[end]))
1119         end++;
1120
1121       unsigned int len = end - start;
1122       if (len < 3)
1123         {
1124           pos++;
1125           continue;
1126         }
1127
1128       unsigned int len2 = data[start - 2] + (data[start - 1] << 8);
1129       unsigned int len3 = data[start - 1];
1130       int length_bytes;
1131       if (len2 && len2 <= len)
1132         {
1133           length_bytes = 2;
1134           len = len2;
1135         }
1136       else if (len3 && len3 <= len)
1137         {
1138           length_bytes = 1;
1139           len = len3;
1140         }
1141       else
1142         {
1143           pos++;
1144           continue;
1145         }
1146       if (len < 3)
1147         {
1148           pos++;
1149           continue;
1150         }
1151       end = start + len;
1152
1153       unsigned real_start = start - length_bytes;
1154       if (prev_end != real_start)
1155         {
1156           if (print_offsets)
1157             printf ("%04x ", prev_end);
1158           hex_dump (stdout, prev_end, real_start - prev_end);
1159         }
1160       if (print_offsets)
1161         printf ("%04x ", real_start);
1162       printf ("\"%.*s\"\n", 
1163               (int) end - start, (char *) &data[start]);
1164       prev_end = end;
1165       pos = end;
1166     }
1167
1168   exit(0);
1169
1170   return 0;
1171 }