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