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