now reads past cells and dimensions!
[pspp] / dump-spo2.c
1 #include <assert.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <float.h>
5 #include <inttypes.h>
6 #include <stdbool.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include "u8-mbtouc.h"
15
16 static const char *filename;
17 static uint8_t *data;
18 static size_t n;
19
20 int version;
21
22 unsigned int pos;
23
24 #define XSTR(x) #x
25 #define STR(x) XSTR(x)
26 #define WHERE __FILE__":" STR(__LINE__)
27
28 static uint8_t
29 get_byte(void)
30 {
31   return data[pos++];
32 }
33
34 static unsigned int
35 get_u32(void)
36 {
37   uint32_t x;
38   memcpy(&x, &data[pos], 4);
39   pos += 4;
40   return x;
41 }
42
43 static unsigned long long int
44 get_u64(void)
45 {
46   uint64_t x;
47   memcpy(&x, &data[pos], 8);
48   pos += 8;
49   return x;
50 }
51
52 static unsigned int
53 get_be32(void)
54 {
55   uint32_t x;
56   x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
57   pos += 4;
58   return x;
59 }
60
61 static unsigned int
62 get_u16(void)
63 {
64   uint16_t x;
65   memcpy(&x, &data[pos], 2);
66   pos += 2;
67   return x;
68 }
69
70 static double
71 get_double(void)
72 {
73   double x;
74   memcpy(&x, &data[pos], 8);
75   pos += 8;
76   return x;
77 }
78
79 static double __attribute__((unused))
80 get_float(void)
81 {
82   float x;
83   memcpy(&x, &data[pos], 4);
84   pos += 4;
85   return x;
86 }
87
88 static bool
89 match_u32(uint32_t x)
90 {
91   if (get_u32() == x)
92     return true;
93   pos -= 4;
94   return false;
95 }
96
97 bool
98 match_u16(uint16_t x)
99 {
100   if (get_u16() == x)
101     return true;
102   pos -= 2;
103   return false;
104 }
105
106 static void
107 match_u32_assert(uint32_t x, const char *where)
108 {
109   unsigned int y = get_u32();
110   if (x != y)
111     {
112       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
113       exit(1);
114     }
115 }
116 #define match_u32_assert(x) match_u32_assert(x, WHERE)
117
118 static void
119 match_u16_assert(uint16_t x, const char *where)
120 {
121   unsigned int y = get_u16();
122   if (x != y)
123     {
124       fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u\n", where, pos - 2, x, y);
125       exit(1);
126     }
127 }
128 #define match_u16_assert(x) match_u16_assert(x, WHERE)
129
130 static bool __attribute__((unused))
131 match_u64(uint64_t x)
132 {
133   if (get_u64() == x)
134     return true;
135   pos -= 8;
136   return false;
137 }
138
139 static void __attribute__((unused))
140 match_u64_assert(uint64_t x, const char *where)
141 {
142   unsigned long long int y = get_u64();
143   if (x != y)
144     {
145       fprintf(stderr, "%s: 0x%x: expected u64:%lu, got u64:%llu\n", where, pos - 8, x, y);
146       exit(1);
147     }
148 }
149 #define match_u64_assert(x) match_u64_assert(x, WHERE)
150
151 static bool __attribute__((unused))
152 match_be32(uint32_t x)
153 {
154   if (get_be32() == x)
155     return true;
156   pos -= 4;
157   return false;
158 }
159
160 static void
161 match_be32_assert(uint32_t x, const char *where)
162 {
163   unsigned int y = get_be32();
164   if (x != y)
165     {
166       fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y);
167       exit(1);
168     }
169 }
170 #define match_be32_assert(x) match_be32_assert(x, WHERE)
171
172 static bool
173 match_byte(uint8_t b)
174 {
175   if (pos < n && data[pos] == b)
176     {
177       pos++;
178       return true;
179     }
180   else
181     return false;
182 }
183
184 static void
185 match_byte_assert(uint8_t b, const char *where)
186 {
187   if (!match_byte(b))
188     {
189       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
190       exit(1);
191     }
192 }
193 #define match_byte_assert(b) match_byte_assert(b, WHERE)
194
195 static bool
196 match_bytes(int start, const int *bytes, size_t n_bytes)
197 {
198   for (size_t i = 0; i < n_bytes; i++)
199     if (bytes[i] >= 0 && data[start + i] != bytes[i])
200       return false;
201   return true;
202 }
203
204 static char *
205 xmemdup0(const void *p, size_t n)
206 {
207   char *s = malloc(n + 1);
208   memcpy(s, p, n);
209   s[n] = 0;
210   return s;
211 }
212
213 static bool
214 get_bool(void)
215 {
216   if (match_byte(0))
217     return false;
218   match_byte_assert(1);
219   return true;
220 }
221
222 static bool __attribute__((unused))
223 is_ascii(uint8_t p)
224 {
225   return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t';
226 }
227
228 static int
229 count_zeros(const uint8_t *p)
230 {
231   size_t n = 0;
232   while (p[n] == 0)
233     n++;
234   return n;
235 }
236
237 static bool __attribute__((unused))
238 all_utf8(const char *p_, size_t len)
239 {
240   const uint8_t *p = (const uint8_t *) p_;
241   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
242     {
243       ucs4_t uc;
244
245       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
246       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
247         return false;
248     }
249   return true;
250 }
251
252 static char *
253 get_string1(void)
254 {
255   int len = data[pos++];
256   char *s = xmemdup0(&data[pos], len);
257   pos += len;
258   return s;
259 }
260
261 static void
262 match_string1_assert(const char *exp, const char *where)
263 {
264   int start = pos;
265   char *act = get_string1();
266   if (strcmp(act, exp)) 
267     {
268       fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
269               where, start, exp, act);
270       exit(1);
271     }
272 }
273 #define match_string1_assert(x) match_string1_assert(x, WHERE)
274
275 static char *
276 get_string2(void)
277 {
278   int len = data[pos] + data[pos + 1] * 256;
279   char *s = xmemdup0(&data[pos + 2], len);
280   pos += 2 + len;
281   return s;
282 }
283
284 static void
285 match_string2_assert(const char *exp, const char *where)
286 {
287   int start = pos;
288   char *act = get_string2();
289   if (strcmp(act, exp)) 
290     {
291       fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
292               where, start, exp, act);
293       exit(1);
294     }
295 }
296 #define match_string2_assert(x) match_string2_assert(x, WHERE)
297
298 static char *
299 get_string4(const char *where)
300 {
301   if (1
302       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
303       /*&& all_ascii(&data[pos + 4], data[pos])*/)
304     {
305       int len = data[pos] + data[pos + 1] * 256;
306       char *s = malloc(len + 1);
307
308       memcpy(s, &data[pos + 4], len);
309       s[len] = 0;
310       pos += 4 + len;
311       return s;
312     }
313   else
314     {
315       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
316       exit(1);
317     }
318 }
319 #define get_string4() get_string4(WHERE)
320
321 static char *
322 get_padded_string(int len)
323 {
324   char *s = xmemdup0(&data[pos], len);
325   pos += len;
326   return s;
327 }
328
329 static char *
330 get_string_be(const char *where)
331 {
332   if (1
333       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
334       /*&& all_ascii(&data[pos + 4], data[pos])*/)
335     {
336       int len = data[pos + 2] * 256 + data[pos + 3];
337       char *s = malloc(len + 1);
338
339       memcpy(s, &data[pos + 4], len);
340       s[len] = 0;
341       pos += 4 + len;
342       return s;
343     }
344   else
345     {
346       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
347       exit(1);
348     }
349 }
350 #define get_string_be() get_string_be(WHERE)
351
352 static int
353 get_end(void)
354 {
355   int len = get_u32();
356   return pos + len;
357 }
358
359 static void __attribute__((unused))
360 hex_dump(FILE *stream, int ofs, int n)
361 {
362   int n_ascii = 0;
363   for (int i = 0; i < n; i++)
364     {
365       int c = data[ofs + i];
366       n_ascii += is_ascii(c);
367       fprintf(stream, " %02x", c);
368     }
369   if (n_ascii >= 3)
370     {
371       putc(' ', stream);
372       for (int i = 0; i < n; i++)
373         {
374           int c = data[ofs + i];
375           putc(c >= 32 && c < 127 ? c : '.', stream);
376         }
377     }
378   putc('\n', stream);
379 }
380
381 static void __attribute__((unused))
382 char_dump(FILE *stream, int ofs, int n)
383 {
384   for (int i = 0; i < n; i++)
385     {
386       int c = data[ofs + i];
387       putc(c >= 32 && c < 127 ? c : '.', stream);
388     }
389   putc('\n', stream);
390 }
391
392
393 static int
394 compare_int(const void *a_, const void *b_)
395 {
396   const int *a = a_;
397   const int *b = b_;
398   return *a < *b ? -1 : *a > *b;
399 }
400
401
402 static const char *
403 format_name (int format, char *buf)
404 {
405   switch (format)
406     {
407     case 1: return "A";
408     case 2: return "AHEX";
409     case 3: return "COMMA";
410     case 4: return "DOLLAR";
411     case 5: case 40: return "F";
412     case 6: return "IB";
413     case 7: return "PIBHEX";
414     case 8: return "P";
415     case 9: return "PIB";
416     case 10: return "PK";
417     case 11: return "RB";
418     case 12: return "RBHEX";
419     case 15: return "Z";
420     case 16: return "N";
421     case 17: return "E";
422     case 20: return "DATE";
423     case 21: return "TIME";
424     case 22: return "DATETIME";
425     case 23: return "ADATE";
426     case 24: return "JDATE";
427     case 25: return "DTIME";
428     case 26: return "WKDAY";
429     case 27: return "MONTH";
430     case 28: return "MOYR";
431     case 29: return "QYR";
432     case 30: return "WKYR";
433     case 31: return "PCT";
434     case 32: return "DOT";
435     case 33: return "CCA";
436     case 34: return "CCB";
437     case 35: return "CCC";
438     case 36: return "CCD";
439     case 37: return "CCE";
440     case 38: return "EDATE";
441     case 39: return "SDATE";
442     default: sprintf(buf, "(%d)", format); return buf;
443     }
444 }
445
446 static void
447 parse_format(void)
448 {
449   int d = data[pos++];
450   int w = data[pos++];
451   int fmt = data[pos++];
452   char buf[32];
453   printf ("%s%d.%d", format_name(fmt, buf), w, d);
454 }
455
456 static void
457 parse_heading(const char *name)
458 {
459   match_u16_assert(0xffff);
460   match_u16_assert(0);
461   match_string2_assert(name);
462 }
463
464 static void
465 match_zeros_assert(int count, const char *where)
466 {
467   for (int i = 0; i < count; i++)
468     if (data[pos + i])
469       {
470         fprintf (stderr,
471                  "%s: %#x: expected %d zeros here but offset %d is %#"PRIx8"\n",
472                  where, pos, count, i, data[pos + i]);
473         exit (1);
474       }
475   pos += count;
476 }
477 #define match_zeros_assert(count) match_zeros_assert(count, WHERE)
478
479 static void
480 put_safe(const char *s)
481 {
482   while (*s)
483     {
484       if (*s == '\n')
485         printf ("\\n");
486       else if (*s == '\r')
487         printf ("\\r");
488       else if (*s < 0x20 || *s > 0x7e)
489         printf ("\\x%02"PRIx8, (uint8_t) *s);
490       else
491         putchar (*s);
492       s++;
493     }
494 }
495
496 static void
497 parse_DspString(void)
498 {
499   match_byte_assert(1);
500   match_byte_assert(2);
501   match_byte_assert(40);
502   if (!match_byte(0))
503     match_byte_assert(5);
504   match_byte_assert(0);
505   match_byte_assert(1);
506   printf ("DspString(\"");
507   put_safe(get_string1());
508   printf("\")\n");
509 }
510
511 static void
512 match_DspString(void)
513 {                               /* 05 80 */
514   match_byte_assert(5);
515   match_byte_assert(0x80);
516   parse_DspString();
517 }
518
519 static void
520 match_DspSimpleText(void)
521 {                               /* 03 80 */
522   match_byte_assert(3);
523   match_byte_assert(0x80);
524   match_zeros_assert(5);
525   if (!match_byte(0x10))
526     match_byte_assert(0);
527   match_zeros_assert(4);
528 }
529
530 static void
531 match_NavTreeViewItem(void)
532 {                               /* 07 80 */
533   match_byte_assert(7);
534   match_byte_assert(0x80);
535   match_zeros_assert(1);
536   if (!match_byte(0) && !match_byte(7))
537     match_byte_assert(8);
538   match_zeros_assert(3);
539   pos++;
540   match_byte_assert(0);
541   match_byte_assert(1);
542   match_zeros_assert(3);
543   if (!match_byte(0))
544     match_byte_assert(1);
545   match_zeros_assert(5);
546   match_byte_assert(1);
547   match_zeros_assert(5);
548
549   put_safe(get_string1());
550   putc('\n', stdout);
551 }
552
553 static void
554 parse_DspNumber(void)
555 {
556   match_byte_assert(1);
557   parse_format();
558   match_byte_assert(0x80);
559   match_byte(2);
560   printf (" %f", get_double());
561   printf (" \"%s\"\n", get_string1());
562 }
563
564 static void
565 match_DspNumber(void)
566 {
567   match_byte_assert(0x2a);
568   match_byte_assert(0x80);
569   parse_DspNumber();
570 }
571
572 static void
573 match_DspCell(void)
574 {                               /* 27 80 */
575   match_byte_assert(0x27);
576   match_byte_assert(0x80);
577   match_byte_assert(0);
578   match_DspSimpleText();
579   if (data[pos] == 5)
580     match_DspString();
581   else if (data[pos] == 0x2a)
582     match_DspNumber();
583   else
584     assert(0);
585 }
586
587 static void
588 parse_PMModelItemInfo(void)
589 {                               /* 54 80 */
590   match_byte_assert(0);
591   pos += 1;                     /* Counter */
592   match_zeros_assert(7);
593   pos += 3;
594   if (!match_byte(0))
595     match_byte_assert(0xe);
596   match_byte_assert(0);
597 }
598
599 static void
600 match_PMModelItemInfo(void)
601 {                               /* 54 80 */
602   match_byte_assert(0x54);
603   match_byte_assert(0x80);
604   parse_PMModelItemInfo();
605   match_DspSimpleText();
606   match_DspString();
607 }
608
609 static void
610 match_PMPivotItemTree(void)
611 {                               /* 52 80 */
612   match_byte_assert(0x52);
613   match_byte_assert(0x80);
614   match_byte_assert(0);
615   match_PMModelItemInfo();
616 }
617
618 int
619 main(int argc, char *argv[])
620 {
621   bool print_offsets = false;
622   for (;;)
623     {
624       int c = getopt (argc, argv, "o");
625       if (c == -1)
626         break;
627
628       switch (c)
629         {
630         case 'o':
631           print_offsets = true;
632           break;
633
634         case '?':
635           exit (-1);
636         }
637     }
638   if (argc - optind != 1)
639     {
640       fprintf (stderr, "usage: %s FILE.bin", argv[0]);
641       exit (1);
642     }
643
644   const char *filename = argv[optind];
645   int fd = open(filename, O_RDONLY);
646   if (fd < 0)
647     {
648       fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
649       exit (1);
650     }
651
652   struct stat s;
653   if (fstat(fd, &s))
654     {
655       perror("fstat");
656       exit(1);
657     }
658   n = s.st_size;
659   data = malloc(n);
660   if (!data)
661     {
662       perror("malloc");
663       exit(1);
664     }
665   if (read(fd, data, n) != n)
666     {
667       perror("read");
668       exit(1);
669     }
670   close(fd);
671
672   setvbuf (stdout, NULL, _IOLBF, 0);
673
674   match_byte_assert(4);
675   match_u32_assert(0);
676   match_string1_assert("SPSS Output Document");
677   match_u32_assert(1);
678   match_byte_assert(0x63);
679
680   parse_heading("NavRoot");
681   match_byte_assert(2);
682   match_zeros_assert(32);
683
684   parse_heading("DspSimpleText");
685   match_zeros_assert(10);
686
687   parse_heading("DspString");
688   parse_DspString();
689
690   parse_heading("NavTreeViewItem");
691   match_byte_assert(0);
692   match_u32_assert(0);
693   match_byte_assert(2);
694   match_byte_assert(0);
695   match_byte_assert(1);
696   match_zeros_assert(9);
697   match_u32_assert(1);
698   assert (pos == 0xb0);
699
700   pos += 0x28;
701   match_zeros_assert(5);
702   if (match_u32(8500))
703     match_u32_assert(11000);
704   else
705     {
706       match_u32_assert(11000);
707       match_u32_assert(8500);
708     }
709   pos = 0x105;
710   match_string1_assert("(Continued)");
711   match_byte_assert(1);
712   match_byte_assert(1);
713   match_zeros_assert(3);
714   get_string4();                /* page title */
715   match_byte_assert(1);
716   match_byte_assert(1);
717   match_zeros_assert(3);
718   get_string4();                /* page number */
719   match_byte_assert(0);
720   pos += 2;
721   match_u16_assert(2);
722
723   parse_heading("NavLog");
724   pos = 0x36b;
725   puts(get_padded_string(32));
726   if (!match_u32(80))
727     match_u32_assert(132);
728   match_zeros_assert(8);
729   match_u32_assert(1);
730   get_string4();
731   match_byte_assert(0);
732
733   parse_heading("NavHead");
734   match_byte_assert(2);
735   match_zeros_assert(24);
736   match_u32_assert(1);
737   match_u32_assert(0);
738   match_DspSimpleText();
739   match_DspString();
740   match_NavTreeViewItem();
741   match_zeros_assert(3);
742
743   parse_heading("NavTitle");
744   pos += 33;
745   match_DspSimpleText();
746   match_DspString();
747   match_NavTreeViewItem();
748
749   match_byte_assert(1);
750   match_byte_assert(1);
751   match_u32_assert(-19);
752   match_zeros_assert(12);
753   match_byte_assert(0xbc);
754   match_byte_assert(2);
755   match_zeros_assert(9);
756   match_byte_assert(0x22);
757   puts(get_padded_string(32));
758   match_u32_assert(80);
759   match_zeros_assert(8);
760   match_u32_assert(1);
761   get_string4();
762   match_byte_assert(0);
763
764   parse_heading("NavNote");
765   match_byte_assert(2);
766   match_zeros_assert(8);
767   match_u32_assert(24);
768   if (!match_u32(0))
769     match_u32_assert(-40);
770   pos += 8;
771   match_u32_assert(2);
772   match_u32_assert(1);
773   match_DspSimpleText();
774   match_DspString();
775   match_NavTreeViewItem();
776   match_byte_assert(1);
777
778   parse_heading("PTPivotController");
779   match_byte_assert(2);
780   pos += 8;
781   match_u32_assert(100);
782   match_u32_assert(100);
783   match_u32_assert(100);
784   match_u32_assert(100);
785
786   parse_heading("PVPivotView");
787   match_u32_assert(5);
788   match_byte_assert(0);
789
790   parse_heading("PMPivotModel");
791   match_byte_assert(3);
792
793   parse_heading("NDimensional__DspCell");
794   match_byte_assert(0);
795   match_u32_assert(1);
796
797   parse_heading("IndexedCollection");
798   match_byte_assert(0);
799   pos++;
800   match_zeros_assert(3);
801   match_byte_assert(1);
802   match_byte_assert(0);
803
804   parse_heading("DspCell");
805   match_byte_assert(0);
806   match_DspSimpleText();
807
808   parse_heading("DspNumber");
809   parse_DspNumber();
810
811   match_DspCell();
812   match_DspCell();
813   match_DspCell();
814   match_DspCell();
815   match_DspCell();
816   match_DspCell();
817   match_DspCell();
818   while (data[pos] == 0)
819     pos++;
820
821   match_DspCell();
822   match_DspCell();
823   while (data[pos] == 0)
824     pos++;
825   match_DspCell();
826   match_DspCell();
827   match_DspCell();
828
829   match_byte_assert(1);
830   match_byte_assert(0);
831   puts(get_string1());
832   match_u32_assert(0);
833   puts(get_string1());
834
835   match_byte_assert(0);
836   match_byte_assert(1);
837   match_byte_assert(0);
838   match_byte_assert(0);
839   match_byte_assert(0);
840   match_byte_assert(1);
841   match_byte_assert(0);
842
843   parse_heading("PMPivotItemTree");
844   match_byte_assert(0);
845
846   parse_heading("AbstractTreeBranch");
847   match_byte_assert(0);
848
849   parse_heading("PMModelItemInfo");
850   parse_PMModelItemInfo();
851   match_DspSimpleText();
852   match_DspString();
853
854   match_u32_assert(7);
855   match_PMPivotItemTree();
856
857   match_u32_assert(0);
858   match_PMPivotItemTree();
859
860   match_u32_assert(0);
861   match_PMPivotItemTree();
862
863   match_u32_assert(6);
864   match_PMPivotItemTree();
865
866   match_u32_assert(0);
867   match_PMPivotItemTree();
868
869   match_u32_assert(0);
870   match_PMPivotItemTree();
871
872   match_u32_assert(0);
873   match_PMPivotItemTree();
874
875   match_u32_assert(0);
876   match_PMPivotItemTree();
877
878   match_u32_assert(0);
879   match_PMPivotItemTree();
880
881   match_u32_assert(0);
882   match_PMPivotItemTree();
883
884   match_u32_assert(2);
885   match_PMPivotItemTree();
886
887   match_u32_assert(0);
888   match_PMPivotItemTree();
889
890   match_u32_assert(0);
891   match_PMPivotItemTree();
892
893   match_u32_assert(0);
894   match_PMPivotItemTree();
895
896   match_u32_assert(0);
897   match_PMPivotItemTree();
898
899   match_u32_assert(2);
900   match_PMPivotItemTree();
901
902   match_u32_assert(0);
903   match_PMPivotItemTree();
904
905   match_u32_assert(0);
906
907   /* ...unknown... */
908
909   while (data[pos] != 0xff || data[pos + 1] != 0xff)
910     pos++;
911   parse_heading("PVViewDimension");
912
913   printf ("%#x: end of successful parse\n", pos);
914
915   return 0;
916 }