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