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