Make dump_nested() quiet.
[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(void)
212 {
213   int subn = get_u32 ();
214 #if 0
215   fprintf (stderr, "nested %d bytes: ", subn);
216   dump_raw(stderr, pos, pos + subn, "");
217   putc('\n', stderr);
218 #endif
219   pos += subn;
220 }
221
222 static void
223 dump_value_31(void)
224 {
225   if (match_byte (0x31))
226     {
227       if (match_u32 (0))
228         {
229           if (match_u32 (1))
230             get_string();
231           else
232             match_u32_assert (0);
233           dump_nested();
234         }
235       else if (match_u32 (1))
236         {
237           printf("(footnote %d) ", get_u32());
238           match_byte_assert (0);
239           match_byte_assert (0);
240           dump_nested();
241         }
242       else if (match_u32 (2))
243         {
244           printf("(special 2)");
245           match_byte_assert(0);
246           match_byte_assert(0);
247           if (!match_u32 (2))
248             match_u32_assert(1);
249           match_byte_assert(0);
250           match_byte_assert(0);
251           dump_nested();
252         }
253       else
254         {
255           match_u32_assert(3);
256           printf("(special 3)");
257           match_byte_assert(0);
258           match_byte_assert(0);
259           match_byte_assert(1);
260           match_byte_assert(0);
261           dump_nested();
262           dump_nested();
263         }
264     }
265   else
266     match_byte_assert (0x58);
267 }
268
269 static void
270 dump_value__(int level, bool match1)
271 {
272   for (int i = 0; i <= level; i++)
273     printf ("    ");
274
275   match_byte(0);
276   match_byte(0);
277   match_byte(0);
278   match_byte(0);
279
280   if (match_byte (3))
281     {
282       char *s1 = get_string();
283       dump_value_31();
284       char *s2 = get_string();
285       char *s3 = get_string();
286       if (strcmp(s1, s3))
287         printf("strings \"%s\", \"%s\" and \"%s\"", s1, s2, s3);
288       else
289         printf("string \"%s\" and \"%s\"", s1, s2);
290       if (!match_byte (0))
291         match_byte_assert(1);
292       if (match1)
293         match_byte (1);
294     }
295   else if (match_byte (5))
296     {
297       dump_value_31();
298       printf ("variable \"%s\"", get_string());
299       get_string();
300       if (!match_byte(1) && !match_byte(2))
301         match_byte_assert(3);
302     }
303   else if (match_byte (2))
304     {
305       unsigned int format;
306       char *var, *vallab;
307       double value;
308
309       match_byte_assert (0x58);
310       format = get_u32 ();
311       value = get_double ();
312       var = get_string ();
313       vallab = get_string ();
314       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
315               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
316       if (!match_byte (1) && !match_byte(2))
317         match_byte_assert (3);
318     }
319   else if (match_byte (4))
320     {
321       unsigned int format;
322       char *var, *vallab, *value;
323
324       match_byte_assert (0x58);
325       format = get_u32 ();
326       vallab = get_string ();
327       var = get_string ();
328       if (!match_byte(1) && !match_byte(2))
329         match_byte_assert (3);
330       value = get_string ();
331       printf ("value \"%s\" format %d(%d.%d) var \"%s\" vallab \"%s\"",
332               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
333     }
334   else if (match_byte (1))
335     {
336       unsigned int format;
337       double value;
338
339       dump_value_31();
340       format = get_u32 ();
341       value = get_double ();
342       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
343       if (match1)
344         match_byte (1);
345     }
346   else
347     {
348       dump_value_31();
349
350       char *base = get_string();
351       int x = get_u32();
352       printf ("\"%s\" with %d variables:\n", base, x);
353       for (int i = 0; i < x; i++)
354         {
355           int y = get_u32();
356           if (!y)
357             y = 1;
358           else
359             match_u32_assert(0);
360           for (int j = 0; j <= level; j++)
361             printf ("    ");
362           printf("variable %d has %d values:\n", i, y);
363           for (int j = 0; j < y; j++)
364             {
365               dump_value__ (level + 1, false);
366               putchar('\n');
367             }
368         }
369     }
370 }
371
372 static int
373 compare_int(const void *a_, const void *b_)
374 {
375   const int *a = a_;
376   const int *b = b_;
377   return *a < *b ? -1 : *a > *b;
378 }
379
380 static void
381 check_permutation(int *a, int n, const char *name)
382 {
383   int b[n];
384   memcpy(b, a, n * sizeof *a);
385   qsort(b, n, sizeof *b, compare_int);
386   for (int i = 0; i < n; i++)
387     if (b[i] != i)
388       {
389         fprintf(stderr, "bad %s permutation:", name);
390         for (int i = 0; i < n; i++)
391           fprintf(stderr, " %d", a[i]);
392         putc('\n', stderr);
393         exit(1);
394       }
395 }
396
397 static void
398 dump_category(int level, int *indexes, int *n_indexes)
399 {
400   dump_value__ (level, true);
401   match_byte(0);
402   match_byte(0);
403   match_byte(0);
404
405   if (match_u32 (1))
406     match_byte (0);
407   else if (match_byte (1))
408     {
409       match_byte (0);
410       if (!match_u32 (2))
411         match_u32_assert (1);
412       match_byte (0);
413     }
414   else if (!match_u32(2))
415     match_u32_assert (0);
416
417   int indx = get_u32();
418   int n_categories = get_u32();
419   if (indx != -1)
420     {
421       if (n_categories != 0)
422         {
423           fprintf(stderr, "index not -1 but subcategories\n");
424           exit(1);
425         }
426       indexes[(*n_indexes)++] = indx;
427     }
428   if (n_categories > 0)
429     printf (", %d subcategories:", n_categories);
430   else
431     printf (", index %d", indx);
432   printf("\n");
433   for (int i = 0; i < n_categories; i++)
434     dump_category (level + 1, indexes, n_indexes);
435 }
436
437 static void
438 dump_dim(void)
439 {
440   int n_categories;
441   printf("next dim\n");
442   dump_value__ (0, false);
443
444   /* This byte is usually 0x02 but 0x00 and 0x75 (!) have also been spotted. */
445   pos++;
446
447   if (!match_byte(0) && !match_byte(1))
448     match_byte_assert(2);
449   if (!match_u32(0))
450     match_u32_assert(2);
451   if (!match_byte(0))
452     match_byte_assert(1);
453   if (!match_byte(0))
454     match_byte_assert(1);
455   match_byte_assert(1);
456   static int dim_indx = 0;
457   match_u32_assert(dim_indx++);
458   n_categories = get_u32();
459   printf("%d nested categories\n", n_categories);
460
461   int indexes[1024];
462   int n_indexes = 0;
463   for (int i = 0; i < n_categories; i++)
464     dump_category (0, indexes, &n_indexes);
465   check_permutation(indexes, n_indexes, "categories");
466 }
467
468 int n_dims;
469 static void
470 dump_dims(void)
471 {
472   n_dims = get_u32();
473   printf ("%u dimensions\n", n_dims);
474   for (int i = 0; i < n_dims; i++)
475     {
476       printf("\n");
477       dump_dim ();
478     }
479 }
480
481 static void
482 dump_data(void)
483 {
484   /* The first three numbers add to the number of dimensions. */
485   int t = get_u32();
486   t += get_u32();
487   match_u32_assert(n_dims - t);
488
489   /* The next n_dims numbers are a permutation of the dimension numbers. */
490   int a[n_dims];
491   for (int i = 0; i < n_dims; i++)
492     a[i] = get_u32();
493   check_permutation(a, n_dims, "dimensions");
494
495   int x = get_u32();
496   printf ("%d data values, starting at %08x\n", x, pos);
497   for (int i = 0; i < x; i++)
498     {
499       printf("%08x, index %d:\n", pos, get_u32());
500       match_u32_assert(0);
501       dump_value__(0, false);
502       putchar('\n');
503     }
504 }
505
506 static void
507 dump_title(void)
508 {
509   pos = 0x27;
510   dump_value__(0, true); putchar('\n');
511   dump_value__(0, true); putchar('\n');
512   match_byte_assert(0x31);
513   dump_value__(0, true); putchar('\n');
514   match_byte(0);
515   match_byte_assert(0x58);
516   if (match_byte(0x31))
517     {
518       dump_value__(0, false); putchar('\n');
519     }
520   else
521     match_byte_assert(0x58);
522
523
524   int n_footnotes = get_u32();
525   if (n_footnotes >= 20)
526     {
527       fprintf(stderr, "%08x: %d footnotes\n", pos - 4, n_footnotes);
528       exit(1);
529     }
530
531   printf("------\n%d footnotes\n", n_footnotes);
532   if (n_footnotes < 20)
533     {
534       for (int i = 0; i < n_footnotes; i++)
535         {
536           printf("footnote %d:\n", i);
537           dump_value__(0, false);
538           if (match_byte (0x31))
539             {
540               /* Custom footnote marker string. */
541               match_byte_assert(3);
542               get_string();
543               match_byte_assert(0x58);
544               match_u32_assert(0);
545               get_string();
546             }
547           else
548             match_byte_assert (0x58);
549           printf("(%d)\n", get_u32());
550         }
551     }
552 }
553
554 static int
555 find_dimensions(void)
556 {
557   {
558     const char dimensions[] = "-,,,.\0";
559     int x = try_find_tail(dimensions, sizeof dimensions - 1);
560     if (x)
561       return x;
562   }
563
564   const char dimensions[] = "-,,, .\0";
565   return find_tail(dimensions, sizeof dimensions - 1);
566 }
567
568 static void
569 dump_fonts(void)
570 {
571   printf("fonts: offset=%08x\n", pos);
572   match_byte(0);
573   for (int i = 1; i <= 8; i++)
574     {
575       printf("%08x: font %d, ", pos, i);
576       match_byte_assert(i);
577       match_byte_assert(0x31);
578       printf("%s, ", get_string());
579       match_byte_assert(0);
580       match_byte_assert(0);
581       if (!match_byte(0x40) && !match_byte(0x20) && !match_byte(0x80) && !match_byte(0x10))
582         match_byte_assert(0x50);
583       if (!match_byte(0x41))
584         match_byte_assert(0x51);
585       if (!match_u32(0))
586         match_u32_assert(1);
587       match_byte_assert(0);
588       pos++;
589       pos++;
590       match_byte_assert(0);
591       match_byte_assert(0);
592       if (!match_u32(0) && !match_u32(1) && !match_u32(2))
593         match_u32_assert(3);
594       printf ("%s, ", get_string());
595       printf ("%s, ", get_string());
596       match_u32_assert(0);
597       match_u32_assert(0);
598       match_byte_assert(0);
599       get_u32();
600       get_u32();
601       get_u32();
602       get_u32();
603       putchar('\n');
604     }
605
606   match_u32_assert(240);
607   pos += 240;
608
609   match_u32_assert(18);
610   pos += 18;
611
612   if (match_u32(117))
613     pos += 117;
614   else
615     {
616       match_u32_assert(142);
617       pos += 142;
618     }
619
620   int count = get_u32();
621   pos += 4 * count;
622
623   char *encoding = get_string();
624   printf("encoding=%s\n", encoding);
625
626   if (!match_u32(0))
627     match_u32_assert(UINT32_MAX);
628   if (!match_byte(0))
629     match_byte_assert(1);
630   match_byte_assert(0);
631   if (!match_byte(0))
632     match_byte_assert(1);
633   if (!match_byte(0x99) && !match_byte(0x98))
634     match_byte_assert(0x97);
635   match_byte_assert(7);
636   match_byte_assert(0);
637   match_byte_assert(0);
638   if (match_byte('.'))
639     match_byte_assert(',');
640   else
641     {
642       match_byte_assert(',');
643       if (!match_byte('.'))
644         match_byte_assert(' ');
645     }
646   match_u32_assert(5);
647   for (int i = 0; i < 5; i++)
648     get_string();
649   pos += get_u32();
650   if (pos != find_dimensions())
651     fprintf (stderr, "%08x / %08x\n", pos, find_dimensions());
652 }
653
654 int
655 main(int argc, char *argv[])
656 {
657   size_t start;
658   struct stat s;
659
660   if (isatty(STDIN_FILENO))
661     {
662       fprintf(stderr, "redirect stdin from a .bin file\n");
663       exit(1);
664     }
665   if (fstat(STDIN_FILENO, &s))
666     {
667       perror("fstat");
668       exit(1);
669     }
670   n = s.st_size;
671   data = malloc(n);
672   if (!data)
673     {
674       perror("malloc");
675       exit(1);
676     }
677   if (read(STDIN_FILENO, data, n) != n)
678     {
679       perror("read");
680       exit(1);
681     }
682
683   if (argc > 1)
684     {
685       if (!strcmp(argv[1], "title0"))
686         {
687           pos = 0x27;
688           if (match_byte (0x03)
689               || (match_byte (0x05) && match_byte (0x58)))
690             printf ("%s\n", get_string());
691           else
692             printf ("<unknown>\n");
693           return 0;
694         }
695       else if (!strcmp(argv[1], "title"))
696         {
697           dump_title();
698           exit(0);
699         }
700       else if (!strcmp(argv[1], "titleraw"))
701         {
702           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
703           start = 0x27;
704           n = find(fonts, sizeof fonts - 1);
705         }
706       else if (!strcmp(argv[1], "fonts"))
707         {
708           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
709           const char styles[] = "\xf0\0\0\0";
710           start = find(fonts, sizeof fonts - 1);
711           n = find(styles, sizeof styles - 1);
712         }
713       else if (!strcmp(argv[1], "styles"))
714         {
715           const char styles[] = "\xf0\0\0\0";
716           const char dimensions[] = "-,,,.\0";
717           start = find(styles, sizeof styles - 1);
718           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
719         }
720       else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
721         {
722           pos = 0;
723           match_byte_assert(1);
724           match_byte_assert(0);
725           match_u32_assert(3);
726           match_byte_assert(1);
727           if (!match_byte(0))
728             match_byte_assert(1);
729           match_byte_assert(0);
730           match_byte_assert(0);
731           if (!match_byte(0))
732             match_byte_assert(1);
733           pos++;
734           match_byte_assert(0);
735           match_byte_assert(0);
736           match_byte_assert(0);
737           dump_title ();
738           dump_fonts();
739           dump_dims ();
740           printf("\n\ndata:\n");
741           dump_data ();
742           match_byte (1);
743           if (pos != n)
744             {
745               fprintf (stderr, "%x / %x\n", pos, n);
746               exit(1);
747             }
748           exit(0);
749         }
750       else
751         {
752           fprintf (stderr, "unknown section %s\n", argv[1]);
753           exit(1);
754         }
755     }
756   else
757     start = 0x27;
758
759   dump_raw(stdout, start, n, "\n");
760
761   return 0;
762 }