Factor out more code.
[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 char *
128 get_string(const char *where)
129 {
130   if (1
131       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
132       /*&& all_ascii(&data[pos + 4], data[pos])*/)
133     {
134       int len = data[pos] + data[pos + 1] * 256;
135       char *s = malloc(len + 1);
136
137       memcpy(s, &data[pos + 4], len);
138       s[len] = 0;
139       pos += 4 + len;
140       return s;
141     }
142   else
143     {
144       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
145       exit(1);
146     }
147 }
148 #define get_string() get_string(WHERE)
149
150 static void
151 dump_value_31(void)
152 {
153   if (match_byte (0x31))
154     {
155       if (match_u32 (0))
156         {
157           if (match_u32 (1))
158             get_string();
159           else
160             match_u32_assert (0);
161           int subn = get_u32 ();
162           printf ("nested %d bytes", subn);
163           pos += subn;
164         }
165       else if (match_u32 (1))
166         {
167           printf("(footnote %d) ", get_u32());
168           match_byte_assert (0);
169           match_byte_assert (0);
170           int subn = get_u32 ();
171           printf ("nested %d bytes", subn);
172           pos += subn;
173         }
174       else if (match_u32 (2))
175         {
176           printf("(special 2)");
177           match_byte_assert(0);
178           match_byte_assert(0);
179           if (!match_u32 (2))
180             match_u32_assert(1);
181           match_byte_assert(0);
182           match_byte_assert(0);
183           int subn = get_u32 ();
184           printf ("nested %d bytes", subn);
185           pos += subn;
186         }
187       else
188         {
189           match_u32_assert(3);
190           printf("(special 3)");
191           match_byte_assert(0);
192           match_byte_assert(0);
193           match_byte_assert(1);
194           match_byte_assert(0);
195           int subn = get_u32 ();
196           printf ("nested %d bytes, ", subn);
197           pos += subn;
198           subn = get_u32 ();
199           printf ("nested %d bytes, ", subn);
200           pos += subn;
201         }
202     }
203   else
204     match_byte_assert (0x58);
205 }
206
207 static void dump_value(int level);
208
209 static void
210 dump_value__(int level, bool match1)
211 {
212   if (match_byte (3))
213     {
214       char *s1 = get_string();
215       dump_value_31();
216       char *s2 = get_string();
217       char *s3 = get_string();
218       if (strcmp(s1, s3))
219         printf("strings \"%s\", \"%s\" and \"%s\"", s1, s2, s3);
220       else
221         printf("string \"%s\" and \"%s\"", s1, s2);
222       if (!match_byte (0))
223         match_byte_assert(1);
224       if (match1)
225         match_byte (1);
226     }
227   else if (match_byte (5))
228     {
229       dump_value_31();
230       printf ("variable \"%s\"", get_string());
231       get_string();
232       if (!match_byte(1) && !match_byte(2))
233         match_byte_assert(3);
234     }
235   else if (match_byte (2))
236     {
237       unsigned int format;
238       char *var, *vallab;
239       double value;
240
241       match_byte_assert (0x58);
242       format = get_u32 ();
243       value = get_double ();
244       var = get_string ();
245       vallab = get_string ();
246       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
247               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
248       if (!match_byte (1) && !match_byte(2))
249         match_byte_assert (3);
250     }
251   else if (match_byte (4))
252     {
253       unsigned int format;
254       char *var, *vallab, *value;
255
256       match_byte_assert (0x58);
257       format = get_u32 ();
258       vallab = get_string ();
259       var = get_string ();
260       if (!match_byte(1) && !match_byte(2))
261         match_byte_assert (3);
262       value = get_string ();
263       printf ("value \"%s\" format %d(%d.%d) var \"%s\" vallab \"%s\"",
264               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
265     }
266   else if (match_byte (1))
267     {
268       unsigned int format;
269       double value;
270
271       dump_value_31();
272       format = get_u32 ();
273       value = get_double ();
274       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
275       if (match1)
276         match_byte (1);
277     }
278   else
279     {
280       dump_value_31();
281       char *base = get_string();
282
283       int x = get_u32();
284       printf ("\"%s\" with %d variables:\n", base, x);
285       if (match_u32(0))
286         {
287           for (int i = 0; i < x; i++)
288             {
289               dump_value (level+1);
290               putchar('\n');
291             }
292         }
293       else
294         {
295           for (int i = 0; i < x; i++)
296             {
297               int y = get_u32();
298               match_u32_assert(0);
299               for (int j = 0; j <= level; j++)
300                 printf ("    ");
301               printf("variable %d has %d values:\n", i, y);
302               for (int j = 0; j < y; j++)
303                 {
304                   match_byte(0);
305                   if (match_byte(3))
306                     {
307                       char *a = get_string();
308                       match_byte_assert(0x58);
309                       char *b = get_string();
310                       char *c = get_string();
311                       for (int k = 0; k <= level + 1; k++)
312                         printf ("    ");
313                       printf ("\"%s\", \"%s\", \"%s\"", a, b, c);
314                     }
315                   else
316                     dump_value (level+1);
317
318                   match_byte(0);
319                   match_byte(0);
320                   match_byte(0);
321                   match_byte(0);
322                   putchar('\n');
323                 }
324             }
325         }
326     }
327 }
328
329 static void
330 dump_value(int level)
331 {
332   for (int i = 0; i <= level; i++)
333     printf ("    ");
334
335   match_byte (0);
336   match_byte (0);
337   match_byte (0);
338   match_byte (0);
339   dump_value__(level, true);
340   match_byte(0);
341   match_byte(0);
342   match_byte(0);
343 }
344
345 static void
346 dump_dim_value(int level)
347 {
348   for (int i = 0; i <= level; i++)
349     printf ("    ");
350
351   if (data[pos] == 3 || data[pos] == 5)
352     dump_value__(level, true);
353   else
354     dump_value(level);
355 }
356
357 static void
358 dump_category(int level)
359 {
360   dump_value (level);
361
362   if (match_u32 (1))
363     match_byte (0);
364   else if (match_byte (1))
365     {
366       match_byte (0);
367       if (!match_u32 (2))
368         match_u32_assert (1);
369       match_byte (0);
370     }
371   else if (!match_u32(2))
372     match_u32_assert (0);
373
374   get_u32 ();
375
376   int n_categories = get_u32();
377   if (n_categories > 0)
378     printf (", %d subcategories:", n_categories);
379   printf("\n");
380   for (int i = 0; i < n_categories; i++)
381     dump_category (level + 1);
382 }
383
384 static void
385 dump_dim(void)
386 {
387   int n_categories;
388   printf("next dim\n");
389   dump_dim_value(0);
390
391   /* This byte is usually 0x02 but 0x00 and 0x75 (!) have also been spotted. */
392   pos++;
393
394   if (!match_byte(0) && !match_byte(1))
395     match_byte_assert(2);
396   if (!match_u32(0))
397     match_u32_assert(2);
398   if (!match_byte(0))
399     match_byte_assert(1);
400   match_byte(0);
401   match_byte(0);
402   match_byte(0);
403   match_byte(0);
404   get_u32();
405   match_byte(0);
406   match_byte(0);
407   match_byte(0);
408   match_byte(0);
409   n_categories = get_u32();
410   printf("%d nested categories\n", n_categories);
411   for (int i = 0; i < n_categories; i++)
412     dump_category (0);
413 }
414
415 int n_dims;
416 static void
417 dump_dims(void)
418 {
419   n_dims = get_u32();
420   printf ("%u dimensions\n", n_dims);
421   for (int i = 0; i < n_dims; i++)
422     {
423       printf("\n");
424       dump_dim ();
425     }
426 }
427
428 static void
429 dump_data_value(void)
430 {
431   match_byte(0);
432   match_byte(0);
433   match_byte(0);
434   match_byte(0);
435   if (data[pos] == 2 || data[pos] == 4 || data[pos] == 3 || data[pos] == 1)
436     dump_value__(0, false);
437   else if (data[pos] == 5)
438     dump_value (0);
439   else
440     {
441       dump_value_31();
442       char *base = get_string();
443       int x = get_u32();
444       printf ("\"%s\"; %d variables:\n", base, x);
445       for (int i = 0; i < x; i++)
446         {
447           int y = get_u32();
448           if (!y)
449             y = 1;
450           else
451             match_u32_assert(0);
452           for (int j = 0; j <= 0; j++)
453             printf ("    ");
454           printf("variable %d has %d values:\n", i, y);
455           for (int j = 0; j < y; j++)
456             dump_data_value();
457         }
458     }
459 }
460
461 static void
462 dump_data(void)
463 {
464 #if 1
465   int a[16];
466   for (int i = 0; i < 3 + n_dims; i++)
467     a[i] = get_u32();
468   printf ("data intro:");
469   for (int i = 0; i < 3 + n_dims; i++)
470     printf(" %d", a[i]);
471   printf("\n");
472 #else
473   fprintf (stderr,"data intro (%d dims):", n_dims);
474   for (int i = 0; i < 3+n_dims; i++)
475     fprintf (stderr," %d", get_u32());
476   fprintf(stderr,"\n");
477 #endif
478   int x = get_u32();
479   printf ("%d data values, starting at %08x\n", x, pos);
480   for (int i = 0; i < x; i++)
481     {
482       printf("%08x, index %d:\n", pos, get_u32());
483       match_u32_assert(0);
484       dump_data_value();
485       putchar('\n');
486     }
487 }
488
489 static void
490 dump_title_value(int level)
491 {
492   for (int i = 0; i <= level; i++)
493     printf ("    ");
494
495   match_byte (0);
496   match_byte (0);
497   match_byte (0);
498   match_byte (0);
499   if (data[pos] == 3 || data[pos] == 2 || data[pos] == 4 || data[pos] == 1)
500     dump_value(level);
501   else if (data[pos] == 5)
502     dump_value__(level, true);
503   else
504     {
505       dump_value_31();
506
507       char *base = get_string();
508       int x = get_u32();
509       printf ("\"%s\" with %d variables:\n", base, x);
510       for (int i = 0; i < x; i++)
511         {
512           int y = get_u32();
513           if (!y)
514             y = 1;
515           else
516             match_u32_assert(0);
517           for (int j = 0; j <= level; j++)
518             printf ("    ");
519           printf("variable %d has %d values:\n", i, y);
520           for (int j = 0; j < y; j++)
521             {
522               dump_title_value (level+1);
523               putchar('\n');
524             }
525         }
526     }
527 }
528
529 static void
530 dump_footnote_value(int level)
531 {
532   for (int i = 0; i <= level; i++)
533     printf ("    ");
534
535   match_byte (0);
536   match_byte (0);
537   match_byte (0);
538   match_byte (0);
539   if (data[pos] == 2 || data[pos] == 4)
540     dump_value(level);
541   else if (data[pos] == 5 || data[pos] == 3 || data[pos] == 1)
542     dump_value__(level, false);
543   else
544     {
545       dump_value_31();
546       char *base = get_string();
547       int x = get_u32();
548       printf ("\"%s\"; %d variables:\n", base, x);
549       for (int i = 0; i < x; i++)
550         {
551           int y = get_u32();
552           if (!y)
553             y = 1;
554           else
555             match_u32_assert(0);
556           for (int j = 0; j <= level; j++)
557             printf ("    ");
558           printf("variable %d has %d values:\n", i, y);
559           for (int j = 0; j < y; j++)
560             {
561               dump_footnote_value (level+1);
562               putchar('\n');
563             }
564         }
565     }
566 }
567
568 static void
569 dump_title(void)
570 {
571   pos = 0x27;
572   dump_title_value(0); putchar('\n');
573   dump_title_value(0); putchar('\n');
574   match_byte_assert(0x31);
575   dump_title_value(0); putchar('\n');
576   match_byte(0);
577   match_byte_assert(0x58);
578   if (match_byte(0x31))
579     {
580       dump_footnote_value(0); putchar('\n');
581     }
582   else
583     match_byte_assert(0x58);
584
585
586   int n_footnotes = get_u32();
587   if (n_footnotes >= 20)
588     {
589       fprintf(stderr, "%08x: %d footnotes\n", pos - 4, n_footnotes);
590       exit(1);
591     }
592
593   printf("------\n%d footnotes\n", n_footnotes);
594   if (n_footnotes < 20)
595     {
596       for (int i = 0; i < n_footnotes; i++)
597         {
598           printf("footnote %d:\n", i);
599           dump_footnote_value(0);
600           match_byte(0);
601           match_byte(0);
602           match_byte(0);
603           match_byte(0);
604           if (match_byte (0x31))
605             {
606               /* Custom footnote marker string. */
607               match_byte_assert(3);
608               get_string();
609               match_byte_assert(0x58);
610               match_u32_assert(0);
611               get_string();
612             }
613           else
614             match_byte_assert (0x58);
615           printf("(%d)\n", get_u32());
616         }
617     }
618 }
619
620 static int
621 find_dimensions(void)
622 {
623   {
624     const char dimensions[] = "-,,,.\0";
625     int x = try_find_tail(dimensions, sizeof dimensions - 1);
626     if (x)
627       return x;
628   }
629
630   const char dimensions[] = "-,,, .\0";
631   return find_tail(dimensions, sizeof dimensions - 1);
632 }
633
634 static void
635 dump_fonts(void)
636 {
637   printf("fonts: offset=%08x\n", pos);
638   match_byte(0);
639   for (int i = 1; i <= 8; i++)
640     {
641       printf("%08x: font %d, ", pos, i);
642       match_byte_assert(i);
643       match_byte_assert(0x31);
644       printf("%s, ", get_string());
645       match_byte_assert(0);
646       match_byte_assert(0);
647       if (!match_byte(0x40) && !match_byte(0x20) && !match_byte(0x80) && !match_byte(0x10))
648         match_byte_assert(0x50);
649       if (!match_byte(0x41))
650         match_byte_assert(0x51);
651       pos += 13;
652       printf ("%s, ", get_string());
653       printf ("%s, ", get_string());
654       match_u32_assert(0);
655       match_u32_assert(0);
656       pos++;
657       get_u32();
658       get_u32();
659       get_u32();
660       get_u32();
661       putchar('\n');
662     }
663
664   match_u32_assert(240);
665   pos += 240;
666
667   match_u32_assert(18);
668   pos += 18;
669
670   if (match_u32(117))
671     pos += 117;
672   else
673     {
674       match_u32_assert(142);
675       pos += 142;
676     }
677
678   int count = get_u32();
679   pos += 4 * count;
680
681   char *encoding = get_string();
682   printf("encoding=%s\n", encoding);
683
684   if (!match_u32(0))
685     match_u32_assert(UINT32_MAX);
686   if (!match_byte(0))
687     match_byte_assert(1);
688   match_byte_assert(0);
689   if (!match_byte(0))
690     match_byte_assert(1);
691   if (!match_byte(0x99) && !match_byte(0x98))
692     match_byte_assert(0x97);
693   match_byte_assert(7);
694   match_byte_assert(0);
695   match_byte_assert(0);
696   if (match_byte('.'))
697     match_byte_assert(',');
698   else
699     {
700       match_byte_assert(',');
701       if (!match_byte('.'))
702         match_byte_assert(' ');
703     }
704   match_u32_assert(5);
705   for (int i = 0; i < 5; i++)
706     get_string();
707   pos += get_u32();
708   if (pos != find_dimensions())
709     fprintf (stderr, "%08x / %08x\n", pos, find_dimensions());
710 }
711
712 int
713 main(int argc, char *argv[])
714 {
715   size_t start;
716   struct stat s;
717
718   if (isatty(STDIN_FILENO))
719     {
720       fprintf(stderr, "redirect stdin from a .bin file\n");
721       exit(1);
722     }
723   if (fstat(STDIN_FILENO, &s))
724     {
725       perror("fstat");
726       exit(1);
727     }
728   n = s.st_size;
729   data = malloc(n);
730   if (!data)
731     {
732       perror("malloc");
733       exit(1);
734     }
735   if (read(STDIN_FILENO, data, n) != n)
736     {
737       perror("read");
738       exit(1);
739     }
740
741   if (argc > 1)
742     {
743       if (!strcmp(argv[1], "title0"))
744         {
745           pos = 0x27;
746           if (match_byte (0x03)
747               || (match_byte (0x05) && match_byte (0x58)))
748             printf ("%s\n", get_string());
749           else
750             printf ("<unknown>\n");
751           return 0;
752         }
753       else if (!strcmp(argv[1], "title"))
754         {
755           dump_title();
756           exit(0);
757         }
758       else if (!strcmp(argv[1], "titleraw"))
759         {
760           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
761           start = 0x27;
762           n = find(fonts, sizeof fonts - 1);
763         }
764       else if (!strcmp(argv[1], "fonts"))
765         {
766           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
767           const char styles[] = "\xf0\0\0\0";
768           start = find(fonts, sizeof fonts - 1);
769           n = find(styles, sizeof styles - 1);
770         }
771       else if (!strcmp(argv[1], "styles"))
772         {
773           const char styles[] = "\xf0\0\0\0";
774           const char dimensions[] = "-,,,.\0";
775           start = find(styles, sizeof styles - 1);
776           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
777         }
778       else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
779         {
780           pos = 0;
781           match_byte_assert(1);
782           match_byte_assert(0);
783           match_u32_assert(3);
784           match_byte_assert(1);
785           if (!match_byte(0))
786             match_byte_assert(1);
787           match_byte_assert(0);
788           match_byte_assert(0);
789           if (!match_byte(0))
790             match_byte_assert(1);
791           pos++;
792           match_byte_assert(0);
793           match_byte_assert(0);
794           match_byte_assert(0);
795           dump_title ();
796           dump_fonts();
797           dump_dims ();
798           printf("\n\ndata:\n");
799           dump_data ();
800           if (pos == n - 1)
801             match_byte_assert (1);
802           if (pos != n)
803             {
804               fprintf (stderr, "%x / %x\n", pos, n);
805               exit(1);
806             }
807           exit(0);
808         }
809       else
810         {
811           fprintf (stderr, "unknown section %s\n", argv[1]);
812           exit(1);
813         }
814     }
815   else
816     start = 0x27;
817
818   for (size_t i = start; i < n; )
819     {
820       if (i + 5 <= n
821           && data[i]
822           //&& !data[i + 1]
823           && !data[i + 2]
824           && !data[i + 3]
825           && i + 4 + data[i] + data[i + 1] * 256 <= n
826           && all_ascii(&data[i + 4], data[i] + data[i + 1] * 256))
827         {
828           fputs("\n\"", stdout);
829           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stdout);
830           fputs("\" ", stdout);
831
832           i += 4 + data[i] + data[i + 1] * 256;
833         }
834       else if (i + 12 <= n
835                && data[i + 1] == 40
836                && data[i + 2] == 5
837                && data[i + 3] == 0)
838         {
839           double d;
840
841           memcpy (&d, &data[i + 4], 8);
842           printf ("F40.%d(%.*f)\n", data[i], data[i], d);
843           i += 12;
844         }
845       else if (i + 12 <= n
846                && data[i + 1] == 40
847                && data[i + 2] == 31
848                && data[i + 3] == 0)
849         {
850           double d;
851
852           memcpy (&d, &data[i + 4], 8);
853           printf ("PCT40.%d(%.*f)\n", data[i], data[i], d);
854           i += 12;
855         }
856       else if (i + 4 <= n
857                && (data[i] && data[i] != 88 && data[i] != 0x41)
858                && !data[i + 1]
859                && !data[i + 2]
860                && !data[i + 3])
861         {
862           printf ("i%d ", data[i]);
863           i += 4;
864         }
865       else
866         {
867           printf("%02x ", data[i]);
868           i++;
869         }
870     }
871
872   return 0;
873 }