More nested data refinement.
[pspp] / dump.c
1 #include <stdbool.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8
9 static uint8_t *data;
10 static size_t n;
11
12 static bool
13 all_ascii(const uint8_t *p, size_t n)
14 {
15   for (size_t i = 0; i < n; i++)
16     if (p[i] < 32 || p[i] > 126)
17       return false;
18   return true;
19 }
20
21 static size_t
22 try_find(const char *target, size_t target_len)
23 {
24   const uint8_t *pos = (const uint8_t *) memmem (data, n, target, target_len);
25   return pos ? pos - data : 0;
26 }
27
28 static size_t
29 try_find_tail(const char *target, size_t target_len)
30 {
31   size_t pos = try_find(target, target_len);
32   return pos ? pos + target_len : 0;
33 }
34
35 static size_t
36 find(const char *target, size_t target_len)
37 {
38   size_t pos = try_find(target, target_len);
39   if (!pos)
40     {
41       fprintf (stderr, "not found\n");
42       exit(1);
43     }
44   return pos;
45 }
46
47 static size_t
48 find_tail(const char *target, size_t target_len)
49 {
50   size_t pos = try_find_tail(target, target_len);
51   if (!pos)
52     {
53       fprintf (stderr, "not found\n");
54       exit(1);
55     }
56   return pos;
57 }
58
59 size_t pos;
60
61 #define XSTR(x) #x
62 #define STR(x) XSTR(x)
63 #define WHERE __FILE__":" STR(__LINE__)
64
65 static unsigned int
66 get_u32(void)
67 {
68   uint32_t x;
69   memcpy(&x, &data[pos], 4);
70   pos += 4;
71   return x;
72 }
73
74 static double
75 get_double(void)
76 {
77   double x;
78   memcpy(&x, &data[pos], 8);
79   pos += 8;
80   return x;
81 }
82
83 static bool
84 match_u32(uint32_t x)
85 {
86   if (get_u32() == x)
87     return true;
88   pos -= 4;
89   return false;
90 }
91
92 static void
93 match_u32_assert(uint32_t x, const char *where)
94 {
95   unsigned int y = get_u32();
96   if (x != y)
97     {
98       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
99       exit(1);
100     }
101 }
102 #define match_u32_assert(x) match_u32_assert(x, WHERE)
103
104 static bool
105 match_byte(uint8_t b)
106 {
107   if (pos < n && data[pos] == b)
108     {
109       pos++;
110       return true;
111     }
112   else
113     return false;
114 }
115
116 static void
117 match_byte_assert(uint8_t b, const char *where)
118 {
119   if (!match_byte(b))
120     {
121       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
122       exit(1);
123     }
124 }
125 #define match_byte_assert(b) match_byte_assert(b, WHERE)
126
127 static void
128 dump_raw(FILE *stream, int start, int end, const char *separator)
129 {
130   for (size_t i = start; i < end; )
131     {
132       if (i + 5 <= n
133           && data[i]
134           //&& !data[i + 1]
135           && !data[i + 2]
136           && !data[i + 3]
137           && i + 4 + data[i] + data[i + 1] * 256 <= end
138           && all_ascii(&data[i + 4], data[i] + data[i + 1] * 256))
139         {
140           fprintf(stream, "%s\"", separator);
141           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stream);
142           fputs("\" ", stream);
143
144           i += 4 + data[i] + data[i + 1] * 256;
145         }
146       else if (i + 12 <= end
147                && data[i + 1] == 40
148                && data[i + 2] == 5
149                && data[i + 3] == 0)
150         {
151           double d;
152
153           memcpy (&d, &data[i + 4], 8);
154           fprintf (stream, "F40.%d(%.*f)%s", data[i], data[i], d, separator);
155           i += 12;
156         }
157       else if (i + 12 <= end
158                && data[i + 1] == 40
159                && data[i + 2] == 31
160                && data[i + 3] == 0)
161         {
162           double d;
163
164           memcpy (&d, &data[i + 4], 8);
165           fprintf (stream, "PCT40.%d(%.*f)%s", data[i], data[i], d, separator);
166           i += 12;
167         }
168       else if (i + 4 <= end
169                && (data[i] && data[i] != 88 && data[i] != 0x41)
170                && !data[i + 1]
171                && !data[i + 2]
172                && !data[i + 3])
173         {
174           fprintf (stream, "i%d ", data[i]);
175           i += 4;
176         }
177       else
178         {
179           fprintf(stream, "%02x ", data[i]);
180           i++;
181         }
182     }
183
184
185 }
186
187 static char *
188 get_string(const char *where)
189 {
190   if (1
191       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
192       /*&& all_ascii(&data[pos + 4], data[pos])*/)
193     {
194       int len = data[pos] + data[pos + 1] * 256;
195       char *s = malloc(len + 1);
196
197       memcpy(s, &data[pos + 4], len);
198       s[len] = 0;
199       pos += 4 + len;
200       return s;
201     }
202   else
203     {
204       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
205       exit(1);
206     }
207 }
208 #define get_string() get_string(WHERE)
209
210 static void
211 dump_nested_string(void)
212 {
213   match_byte_assert (0);
214   match_byte_assert (0);
215   int outer_end = pos + get_u32();
216   int inner_end = pos + get_u32();
217   if (pos != inner_end)
218     {
219       match_u32_assert(0);
220       if (match_byte(0x31))
221         get_string();
222       else
223         match_byte_assert(0x58);
224       if (pos != inner_end)
225         {
226           fprintf(stderr, "inner end discrepancy\n");
227           exit(1);
228         }
229     }
230   match_byte_assert(0x58);
231   match_byte_assert(0x58);
232   if (pos != outer_end)
233     {
234       fprintf(stderr, "outer end discrepancy\n");
235       exit(1);
236     }
237 }
238
239 static void
240 dump_value_31(void)
241 {
242   if (match_byte (0x31))
243     {
244       if (match_u32 (0))
245         {
246           if (match_u32 (1))
247             get_string();
248           else
249             match_u32_assert (0);
250
251           int outer_end = pos + get_u32();
252           int inner_end = pos + get_u32();
253           match_u32_assert(0);
254           if (match_byte(0x31))
255             get_string();
256           else
257             match_byte_assert(0x58);
258           if (pos != inner_end)
259             {
260               fprintf(stderr, "inner end discrepancy\n");
261               exit(1);
262             }
263
264           if (match_byte(0x31))
265             {
266               match_byte(0);
267               match_byte(0);
268               match_byte(0);
269               match_byte_assert(1);
270               get_string();     /* foreground */
271               get_string();     /* background */
272               get_string();     /* font */
273               match_byte_assert(12); /* size? */
274             }
275           else
276             match_byte_assert(0x58);
277           match_byte_assert(0x58);
278           if (pos != outer_end)
279             {
280               fprintf(stderr, "outer end discrepancy\n");
281               exit(1);
282             }
283         }
284       else if (match_u32 (1))
285         {
286           printf("(footnote %d) ", get_u32());
287           dump_nested_string();
288         }
289       else if (match_u32 (2))
290         {
291           printf("(special 2)");
292           match_byte_assert(0);
293           match_byte_assert(0);
294           if (!match_u32 (2))
295             match_u32_assert(1);
296           dump_nested_string(); /* Our corpus doesn't contain any examples with strings though. */
297         }
298       else
299         {
300           match_u32_assert(3);
301           printf("(special 3)");
302           match_byte_assert(0);
303           match_byte_assert(0);
304           match_byte_assert(1);
305           match_byte_assert(0);
306           match_u32_assert(2);
307           dump_nested_string(); /* Our corpus doesn't contain any examples with strings though. */
308         }
309     }
310   else
311     match_byte_assert (0x58);
312 }
313
314 static void
315 dump_value__(int level, bool match1)
316 {
317   for (int i = 0; i <= level; i++)
318     printf ("    ");
319
320   match_byte(0);
321   match_byte(0);
322   match_byte(0);
323   match_byte(0);
324
325   if (match_byte (3))
326     {
327       char *s1 = get_string();
328       dump_value_31();
329       char *s2 = get_string();
330       char *s3 = get_string();
331       if (strcmp(s1, s3))
332         printf("strings \"%s\", \"%s\" and \"%s\"", s1, s2, s3);
333       else
334         printf("string \"%s\" and \"%s\"", s1, s2);
335       if (!match_byte (0))
336         match_byte_assert(1);
337       if (match1)
338         match_byte (1);
339     }
340   else if (match_byte (5))
341     {
342       dump_value_31();
343       printf ("variable \"%s\"", get_string());
344       get_string();
345       if (!match_byte(1) && !match_byte(2))
346         match_byte_assert(3);
347     }
348   else if (match_byte (2))
349     {
350       unsigned int format;
351       char *var, *vallab;
352       double value;
353
354       match_byte_assert (0x58);
355       format = get_u32 ();
356       value = get_double ();
357       var = get_string ();
358       vallab = get_string ();
359       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
360               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
361       if (!match_byte (1) && !match_byte(2))
362         match_byte_assert (3);
363     }
364   else if (match_byte (4))
365     {
366       unsigned int format;
367       char *var, *vallab, *value;
368
369       match_byte_assert (0x58);
370       format = get_u32 ();
371       vallab = get_string ();
372       var = get_string ();
373       if (!match_byte(1) && !match_byte(2))
374         match_byte_assert (3);
375       value = get_string ();
376       printf ("value \"%s\" format %d(%d.%d) var \"%s\" vallab \"%s\"",
377               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
378     }
379   else if (match_byte (1))
380     {
381       unsigned int format;
382       double value;
383
384       dump_value_31();
385       format = get_u32 ();
386       value = get_double ();
387       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
388       if (match1)
389         match_byte (1);
390     }
391   else
392     {
393       dump_value_31();
394
395       char *base = get_string();
396       int x = get_u32();
397       printf ("\"%s\" with %d variables:\n", base, x);
398       for (int i = 0; i < x; i++)
399         {
400           int y = get_u32();
401           if (!y)
402             y = 1;
403           else
404             match_u32_assert(0);
405           for (int j = 0; j <= level; j++)
406             printf ("    ");
407           printf("variable %d has %d values:\n", i, y);
408           for (int j = 0; j < y; j++)
409             {
410               dump_value__ (level + 1, false);
411               putchar('\n');
412             }
413         }
414     }
415 }
416
417 static int
418 compare_int(const void *a_, const void *b_)
419 {
420   const int *a = a_;
421   const int *b = b_;
422   return *a < *b ? -1 : *a > *b;
423 }
424
425 static void
426 check_permutation(int *a, int n, const char *name)
427 {
428   int b[n];
429   memcpy(b, a, n * sizeof *a);
430   qsort(b, n, sizeof *b, compare_int);
431   for (int i = 0; i < n; i++)
432     if (b[i] != i)
433       {
434         fprintf(stderr, "bad %s permutation:", name);
435         for (int i = 0; i < n; i++)
436           fprintf(stderr, " %d", a[i]);
437         putc('\n', stderr);
438         exit(1);
439       }
440 }
441
442 static void
443 dump_category(int level, int *indexes, int *n_indexes)
444 {
445   dump_value__ (level, true);
446   match_byte(0);
447   match_byte(0);
448   match_byte(0);
449
450   if (match_u32 (1))
451     match_byte (0);
452   else if (match_byte (1))
453     {
454       match_byte (0);
455       if (!match_u32 (2))
456         match_u32_assert (1);
457       match_byte (0);
458     }
459   else if (!match_u32(2))
460     match_u32_assert (0);
461
462   int indx = get_u32();
463   int n_categories = get_u32();
464   if (indx != -1)
465     {
466       if (n_categories != 0)
467         {
468           fprintf(stderr, "index not -1 but subcategories\n");
469           exit(1);
470         }
471       indexes[(*n_indexes)++] = indx;
472     }
473   if (n_categories > 0)
474     printf (", %d subcategories:", n_categories);
475   else
476     printf (", index %d", indx);
477   printf("\n");
478   for (int i = 0; i < n_categories; i++)
479     dump_category (level + 1, indexes, n_indexes);
480 }
481
482 static void
483 dump_dim(void)
484 {
485   int n_categories;
486   printf("next dim\n");
487   dump_value__ (0, false);
488
489   /* This byte is usually 0x02 but 0x00 and 0x75 (!) have also been spotted. */
490   pos++;
491
492   if (!match_byte(0) && !match_byte(1))
493     match_byte_assert(2);
494   if (!match_u32(0))
495     match_u32_assert(2);
496   if (!match_byte(0))
497     match_byte_assert(1);
498   if (!match_byte(0))
499     match_byte_assert(1);
500   match_byte_assert(1);
501   static int dim_indx = 0;
502   match_u32_assert(dim_indx++);
503   n_categories = get_u32();
504   printf("%d nested categories\n", n_categories);
505
506   int indexes[1024];
507   int n_indexes = 0;
508   for (int i = 0; i < n_categories; i++)
509     dump_category (0, indexes, &n_indexes);
510   check_permutation(indexes, n_indexes, "categories");
511 }
512
513 int n_dims;
514 static void
515 dump_dims(void)
516 {
517   n_dims = get_u32();
518   printf ("%u dimensions\n", n_dims);
519   for (int i = 0; i < n_dims; i++)
520     {
521       printf("\n");
522       dump_dim ();
523     }
524 }
525
526 static void
527 dump_data(void)
528 {
529   /* The first three numbers add to the number of dimensions. */
530   int t = get_u32();
531   t += get_u32();
532   match_u32_assert(n_dims - t);
533
534   /* The next n_dims numbers are a permutation of the dimension numbers. */
535   int a[n_dims];
536   for (int i = 0; i < n_dims; i++)
537     a[i] = get_u32();
538   check_permutation(a, n_dims, "dimensions");
539
540   int x = get_u32();
541   printf ("%d data values, starting at %08x\n", x, pos);
542   for (int i = 0; i < x; i++)
543     {
544       printf("%08x, index %d:\n", pos, get_u32());
545       match_u32_assert(0);
546       dump_value__(0, false);
547       putchar('\n');
548     }
549 }
550
551 static void
552 dump_title(void)
553 {
554   pos = 0x27;
555   dump_value__(0, true); putchar('\n');
556   dump_value__(0, true); putchar('\n');
557   match_byte_assert(0x31);
558   dump_value__(0, true); putchar('\n');
559   match_byte(0);
560   match_byte_assert(0x58);
561   if (match_byte(0x31))
562     {
563       dump_value__(0, false); putchar('\n');
564     }
565   else
566     match_byte_assert(0x58);
567
568
569   int n_footnotes = get_u32();
570   if (n_footnotes >= 20)
571     {
572       fprintf(stderr, "%08x: %d footnotes\n", pos - 4, n_footnotes);
573       exit(1);
574     }
575
576   printf("------\n%d footnotes\n", n_footnotes);
577   if (n_footnotes < 20)
578     {
579       for (int i = 0; i < n_footnotes; i++)
580         {
581           printf("footnote %d:\n", i);
582           dump_value__(0, false);
583           if (match_byte (0x31))
584             {
585               /* Custom footnote marker string. */
586               match_byte_assert(3);
587               get_string();
588               match_byte_assert(0x58);
589               match_u32_assert(0);
590               get_string();
591             }
592           else
593             match_byte_assert (0x58);
594           printf("(%d)\n", get_u32());
595         }
596     }
597 }
598
599 static int
600 find_dimensions(void)
601 {
602   {
603     const char dimensions[] = "-,,,.\0";
604     int x = try_find_tail(dimensions, sizeof dimensions - 1);
605     if (x)
606       return x;
607   }
608
609   const char dimensions[] = "-,,, .\0";
610   return find_tail(dimensions, sizeof dimensions - 1);
611 }
612
613 static void
614 dump_fonts(void)
615 {
616   printf("fonts: offset=%08x\n", pos);
617   match_byte(0);
618   for (int i = 1; i <= 8; i++)
619     {
620       printf("%08x: font %d, ", pos, i);
621       match_byte_assert(i);
622       match_byte_assert(0x31);
623       printf("%s, ", get_string());
624       match_byte_assert(0);
625       match_byte_assert(0);
626       if (!match_byte(0x40) && !match_byte(0x20) && !match_byte(0x80) && !match_byte(0x10))
627         match_byte_assert(0x50);
628       if (!match_byte(0x41))
629         match_byte_assert(0x51);
630       if (!match_u32(0))
631         match_u32_assert(1);
632       match_byte_assert(0);
633
634       /* OK, this seems really unlikely to be totally correct, but it matches my corpus... */
635       if (!match_u32(0) && !match_u32(2))
636         match_u32_assert(0xfaad);
637
638       if (!match_u32(0) && !match_u32(1) && !match_u32(2))
639         match_u32_assert(3);
640       printf ("%s, ", get_string());
641       printf ("%s, ", get_string());
642       match_u32_assert(0);
643       match_u32_assert(0);
644       match_byte_assert(0);
645
646       /* These seem unlikely to be correct too. */
647       if (i != 3)
648         {
649           match_u32_assert(8);
650           if (!match_u32(10))
651             match_u32_assert(11);
652           match_u32_assert(1);
653         }
654       else
655         {
656           get_u32();
657           if (!match_u32(-1) && !match_u32(8))
658             match_u32_assert(24);
659           if (!match_u32(-1) && !match_u32(2))
660             match_u32_assert(3);
661         }
662
663       /* Who knows? Ranges from -1 to 8 with no obvious pattern. */
664       get_u32();
665     }
666
667   match_u32_assert(240);
668   pos += 240;
669
670   match_u32_assert(18);
671   pos += 18;
672
673   if (match_u32(117))
674     pos += 117;
675   else
676     {
677       match_u32_assert(142);
678       pos += 142;
679     }
680
681   int count = get_u32();
682   pos += 4 * count;
683
684   char *encoding = get_string();
685   printf("encoding=%s\n", encoding);
686
687   if (!match_u32(0))
688     match_u32_assert(UINT32_MAX);
689   if (!match_byte(0))
690     match_byte_assert(1);
691   match_byte_assert(0);
692   if (!match_byte(0))
693     match_byte_assert(1);
694   if (!match_byte(0x99) && !match_byte(0x98))
695     match_byte_assert(0x97);
696   match_byte_assert(7);
697   match_byte_assert(0);
698   match_byte_assert(0);
699   if (match_byte('.'))
700     match_byte_assert(',');
701   else
702     {
703       match_byte_assert(',');
704       if (!match_byte('.'))
705         match_byte_assert(' ');
706     }
707   match_u32_assert(5);
708   for (int i = 0; i < 5; i++)
709     get_string();
710   pos += get_u32();
711   if (pos != find_dimensions())
712     fprintf (stderr, "%08x / %08x\n", pos, find_dimensions());
713 }
714
715 int
716 main(int argc, char *argv[])
717 {
718   size_t start;
719   struct stat s;
720
721   if (isatty(STDIN_FILENO))
722     {
723       fprintf(stderr, "redirect stdin from a .bin file\n");
724       exit(1);
725     }
726   if (fstat(STDIN_FILENO, &s))
727     {
728       perror("fstat");
729       exit(1);
730     }
731   n = s.st_size;
732   data = malloc(n);
733   if (!data)
734     {
735       perror("malloc");
736       exit(1);
737     }
738   if (read(STDIN_FILENO, data, n) != n)
739     {
740       perror("read");
741       exit(1);
742     }
743
744   if (argc > 1)
745     {
746       if (!strcmp(argv[1], "title0"))
747         {
748           pos = 0x27;
749           if (match_byte (0x03)
750               || (match_byte (0x05) && match_byte (0x58)))
751             printf ("%s\n", get_string());
752           else
753             printf ("<unknown>\n");
754           return 0;
755         }
756       else if (!strcmp(argv[1], "title"))
757         {
758           dump_title();
759           exit(0);
760         }
761       else if (!strcmp(argv[1], "titleraw"))
762         {
763           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
764           start = 0x27;
765           n = find(fonts, sizeof fonts - 1);
766         }
767       else if (!strcmp(argv[1], "fonts"))
768         {
769           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
770           const char styles[] = "\xf0\0\0\0";
771           start = find(fonts, sizeof fonts - 1);
772           n = find(styles, sizeof styles - 1);
773         }
774       else if (!strcmp(argv[1], "styles"))
775         {
776           const char styles[] = "\xf0\0\0\0";
777           const char dimensions[] = "-,,,.\0";
778           start = find(styles, sizeof styles - 1);
779           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
780         }
781       else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
782         {
783           pos = 0;
784           match_byte_assert(1);
785           match_byte_assert(0);
786           match_u32_assert(3);
787           match_byte_assert(1);
788           if (!match_byte(0))
789             match_byte_assert(1);
790           match_byte_assert(0);
791           match_byte_assert(0);
792           if (!match_byte(0))
793             match_byte_assert(1);
794           pos++;
795           match_byte_assert(0);
796           match_byte_assert(0);
797           match_byte_assert(0);
798           dump_title ();
799           dump_fonts();
800           dump_dims ();
801           printf("\n\ndata:\n");
802           dump_data ();
803           match_byte (1);
804           if (pos != n)
805             {
806               fprintf (stderr, "%x / %x\n", pos, n);
807               exit(1);
808             }
809           exit(0);
810         }
811       else
812         {
813           fprintf (stderr, "unknown section %s\n", argv[1]);
814           exit(1);
815         }
816     }
817   else
818     start = 0x27;
819
820   dump_raw(stdout, start, n, "\n");
821
822   return 0;
823 }