e8f865062e58ccd6b5e47adec2e8377a1769f9dd
[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       int total_subs = 1;
288
289       if (match_u32 (1))
290         {
291           printf("(footnote %d) ", get_u32());
292           match_byte_assert (0);
293           match_byte_assert (0);
294           int subn = get_u32 ();
295           printf ("nested %d bytes", subn);
296           pos += subn;
297         }
298       else
299         {
300           match_u32_assert (0);
301           match_u32_assert (0);
302           subn = get_u32 ();
303           printf ("nested %d bytes", subn);
304           pos += subn;
305         }
306       printf ("; \"%s\", substitutions:", get_string());
307       for (;;)
308         {
309           int n_subst = get_u32();
310           if (!n_subst)
311             break;
312           printf (" %d", n_subst);
313           total_subs *= n_subst;
314         }
315
316       for (int i = 0; i < total_subs; i++)
317         {
318           putc ('\n', stdout);
319           dump_value (level + 1);
320         }
321     }
322   else
323     {
324       int total_subs = 1;
325
326       match_byte_assert (0x58);
327       printf ("\"%s\" with substitutions:", get_string());
328       for (;;)
329         {
330           int n_subst = get_u32();
331           if (!n_subst)
332             break;
333           printf (" %d", n_subst);
334           total_subs *= n_subst;
335         }
336
337       for (int i = 0; i < total_subs; i++)
338         {
339           putc ('\n', stdout);
340           dump_value (level + 1);
341         }
342     }
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 (match_byte (3))
352     {
353       get_string();
354       if (match_byte (0x31))
355         {
356           match_u32 (1);
357           printf("(footnote %d) ", get_u32());
358           match_byte_assert (0);
359           match_byte_assert (0);
360           int subn = get_u32 ();
361           printf ("nested %d bytes", subn);
362           pos += subn;
363         }
364       else
365         match_byte_assert (0x58);
366       get_string();
367       printf("string \"%s\"", get_string());
368       match_byte (0);
369       match_byte_assert (1);
370       match_byte (0);
371       match_byte (0);
372       match_byte (0);
373       match_byte (1);
374     }
375   else if (match_byte (5))
376     {
377       match_byte_assert (0x58);
378       printf ("variable \"%s\"", get_string());
379       get_string();
380       match_byte_assert (2);
381     }
382   else if (match_byte (2))
383     {
384       unsigned int format;
385       char *var, *vallab;
386       double value;
387
388       match_byte_assert (0x58);
389       format = get_u32 ();
390       value = get_double ();
391       var = get_string ();
392       vallab = get_string ();
393       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
394               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
395       if (!match_u32 (3))
396         match_u32_assert (2);
397       match_byte (0);
398     }
399   else if (match_byte (1))
400     {
401       unsigned int format;
402       double value;
403
404       match_byte_assert (0x58);
405       format = get_u32 ();
406       value = get_double ();
407       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
408       match_byte (1);
409       match_byte (0);
410       match_byte (0);
411       match_byte (0);
412       match_byte (1);
413     }
414   else
415     {
416       int subn;
417       int total_subs = 1;
418
419       match_byte (0);
420       match_byte_assert (0x31);
421       match_u32_assert (0);
422       match_u32_assert (0);
423       subn = get_u32 ();
424       printf ("nested %d bytes", subn);
425       pos += subn;
426       printf ("; \"%s\", substitutions:", get_string());
427       for (;;)
428         {
429           int n_subst = get_u32();
430           if (!n_subst)
431             break;
432           printf (" %d", n_subst);
433           total_subs *= n_subst;
434         }
435
436       for (int i = 0; i < total_subs; i++)
437         {
438           putc ('\n', stdout);
439           dump_value (level + 1);
440         }
441     }
442 }
443
444 static void
445 dump_category(int level)
446 {
447   match_byte (0);
448   match_byte (0);
449   match_byte (0);
450   match_byte (0);
451   dump_value (level);
452
453   if (match_u32 (2))
454     get_u32 ();
455   else if (match_u32 (1))
456     {
457       match_byte (0);
458       match_byte (0);
459       match_byte (0);
460       get_u32 ();
461     }
462   else if (match_byte (1))
463     {
464       match_byte (0);
465       if (!match_u32 (2))
466         match_u32_assert (1);
467       match_byte (0);
468       get_u32();
469     }
470   else
471     {
472       match_u32_assert (0);
473       get_u32 ();
474     }
475
476   int n_categories = get_u32();
477   if (n_categories > 0)
478     printf (", %d subcategories:", n_categories);
479   printf("\n");
480   for (int i = 0; i < n_categories; i++)
481     dump_category (level + 1);
482 }
483
484 static void
485 dump_dim(void)
486 {
487   int n_categories;
488   printf("next dim\n");
489   match_byte(0);
490   if (match_byte(3))
491     {
492       get_string();
493       match_byte_assert(0x58);
494       get_string();
495       printf("string \"%s\": ", get_string());
496       match_byte(1) || match_byte(0);
497     }
498   else if (match_byte(5)) 
499     {
500       match_byte_assert(0x58);
501       printf("variable \"%s\": ", get_string());
502       get_string();
503       if (!match_byte(2))
504         match_byte_assert(3);
505     }
506   else if (match_byte(0x31))
507     {
508       int subn;
509       int total_subs = 1;
510
511       match_u32_assert (0);
512       match_u32_assert (0);
513       subn = get_u32 ();
514       printf ("nested %d bytes", subn);
515       pos += subn;
516       printf ("; \"%s\", substitutions:", get_string());
517       for (;;)
518         {
519           int n_subst = get_u32();
520           if (!n_subst)
521             break;
522           printf (" %d", n_subst);
523           total_subs *= n_subst;
524         }
525
526       for (int i = 0; i < total_subs; i++)
527         {
528           putc ('\n', stdout);
529           dump_dim_value (0);
530         }
531     }
532   else
533     {
534       int total_subs = 1;
535
536       match_byte_assert (0x58);
537       printf ("\"%s\" with substitutions:", get_string());
538       for (;;)
539         {
540           int n_subst = get_u32();
541           if (!n_subst)
542             break;
543           printf (" %d", n_subst);
544           total_subs *= n_subst;
545         }
546
547       for (int i = 0; i < total_subs; i++)
548         {
549           putc ('\n', stdout);
550           dump_dim_value (0);
551         }
552     }
553
554   /* This byte is usually 0x02 but 0x00 and 0x75 (!) have also been spotted. */
555   pos++;
556
557   if (!match_byte(0) && !match_byte(1))
558     match_byte_assert(2);
559   if (!match_u32(0))
560     match_u32_assert(2);
561   if (!match_byte(0))
562     match_byte_assert(1);
563   match_byte(0);
564   match_byte(0);
565   match_byte(0);
566   match_byte(0);
567   get_u32();
568   match_byte(0);
569   match_byte(0);
570   match_byte(0);
571   match_byte(0);
572   n_categories = get_u32();
573   printf("%d nested categories\n", n_categories);
574   for (int i = 0; i < n_categories; i++)
575     dump_category (0);
576 }
577
578 static void
579 dump_dims(void)
580 {
581   int n_dims = get_u32();
582
583   printf ("%u dimensions\n", n_dims);
584   for (int i = 0; i < n_dims; i++)
585     {
586       printf("\n");
587       dump_dim ();
588     }
589 }
590
591 int
592 main(int argc, char *argv[])
593 {
594   size_t start;
595   struct stat s;
596
597   if (isatty(STDIN_FILENO))
598     {
599       fprintf(stderr, "redirect stdin from a .bin file\n");
600       exit(1);
601     }
602   if (fstat(STDIN_FILENO, &s))
603     {
604       perror("fstat");
605       exit(1);
606     }
607   n = s.st_size;
608   data = malloc(n);
609   if (!data)
610     {
611       perror("malloc");
612       exit(1);
613     }
614   if (read(STDIN_FILENO, data, n) != n)
615     {
616       perror("read");
617       exit(1);
618     }
619
620   if (argc > 1)
621     {
622       if (!strcmp(argv[1], "title0"))
623         {
624           pos = 0x27;
625           if (match_byte (0x03)
626               || (match_byte (0x05) && match_byte (0x58)))
627             printf ("%s\n", get_string());
628           else
629             printf ("<unknown>\n");
630           return 0;
631         }
632       if (!strcmp(argv[1], "title"))
633         {
634           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
635           start = 0x27;
636           n = find(fonts, sizeof fonts - 1);
637         }
638       else if (!strcmp(argv[1], "fonts"))
639         {
640           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
641           const char styles[] = "\xf0\0\0\0";
642           start = find(fonts, sizeof fonts - 1);
643           n = find(styles, sizeof styles - 1);
644         }
645       else if (!strcmp(argv[1], "styles"))
646         {
647           const char styles[] = "\xf0\0\0\0";
648           const char dimensions[] = "-,,,.\0";
649           start = find(styles, sizeof styles - 1);
650           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
651         }
652       else if (!strcmp(argv[1], "dimensions"))
653         {
654           {
655             const char dimensions[] = "-,,,.\0";
656             start = try_find_tail(dimensions, sizeof dimensions - 1);
657           }
658
659           if (!start)
660             {
661               const char dimensions[] = "-,,, .\0";
662               start = find_tail(dimensions, sizeof dimensions - 1);
663             }
664
665           pos = start;
666           dump_dims ();
667           return 0;
668         }
669       else
670         {
671           fprintf (stderr, "unknown section %s\n", argv[1]);
672           exit(1);
673         }
674     }
675   else
676     start = 0x27;
677
678   for (size_t i = start; i < n; )
679     {
680       if (i + 5 <= n
681           && data[i]
682           && !data[i + 1]
683           && !data[i + 2]
684           && !data[i + 3]
685           && i + 4 + data[i] <= n
686           && all_ascii(&data[i + 4], data[i]))
687         {
688           fputs("\n\"", stdout);
689           fwrite(&data[i + 4], 1, data[i], stdout);
690           fputs("\" ", stdout);
691
692           i += 4 + data[i];
693         }
694       else if (i + 12 <= n
695                && data[i + 1] == 40
696                && data[i + 2] == 5
697                && data[i + 3] == 0)
698         {
699           double d;
700
701           memcpy (&d, &data[i + 4], 8);
702           printf ("F40.%d(%.*f)\n", data[i], data[i], d);
703           i += 12;
704         }
705       else if (i + 12 <= n
706                && data[i + 1] == 40
707                && data[i + 2] == 31
708                && data[i + 3] == 0)
709         {
710           double d;
711
712           memcpy (&d, &data[i + 4], 8);
713           printf ("PCT40.%d(%.*f)\n", data[i], data[i], d);
714           i += 12;
715         }
716       else if (i + 4 <= n
717                && (data[i] && data[i] != 88 && data[i] != 0x41)
718                && !data[i + 1]
719                && !data[i + 2]
720                && !data[i + 3])
721         {
722           printf ("i%d ", data[i]);
723           i += 4;
724         }
725       else
726         {
727           printf("%02x ", data[i]);
728           i++;
729         }
730     }
731
732   return 0;
733 }