dump: Make it work with updated corpus.
[pspp] / dump.c
1 #include <assert.h>
2 #include <float.h>
3 #include <stdbool.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include "u8-mbtouc.h"
11
12 static uint8_t *data;
13 static size_t n;
14
15 int version;
16
17 static bool
18 all_ascii(const uint8_t *p, size_t n)
19 {
20   for (size_t i = 0; i < n; i++)
21     if (p[i] < 32 || p[i] > 126)
22       return false;
23   return true;
24 }
25
26 static size_t
27 try_find(const char *target, size_t target_len)
28 {
29   const uint8_t *pos = (const uint8_t *) memmem (data, n, target, target_len);
30   return pos ? pos - data : 0;
31 }
32
33 static size_t
34 find(const char *target, size_t target_len)
35 {
36   size_t pos = try_find(target, target_len);
37   if (!pos)
38     {
39       fprintf (stderr, "not found\n");
40       exit(1);
41     }
42   return pos;
43 }
44
45 size_t pos;
46
47 #define XSTR(x) #x
48 #define STR(x) XSTR(x)
49 #define WHERE __FILE__":" STR(__LINE__)
50
51 static unsigned int
52 get_u32(void)
53 {
54   uint32_t x;
55   memcpy(&x, &data[pos], 4);
56   pos += 4;
57   return x;
58 }
59
60 static unsigned int
61 get_u16(void)
62 {
63   uint16_t x;
64   memcpy(&x, &data[pos], 2);
65   pos += 2;
66   return x;
67 }
68
69 static double
70 get_double(void)
71 {
72   double x;
73   memcpy(&x, &data[pos], 8);
74   pos += 8;
75   return x;
76 }
77
78 static bool
79 match_u32(uint32_t x)
80 {
81   if (get_u32() == x)
82     return true;
83   pos -= 4;
84   return false;
85 }
86
87 static void
88 match_u32_assert(uint32_t x, const char *where)
89 {
90   unsigned int y = get_u32();
91   if (x != y)
92     {
93       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
94       exit(1);
95     }
96 }
97 #define match_u32_assert(x) match_u32_assert(x, WHERE)
98
99 static bool
100 match_byte(uint8_t b)
101 {
102   if (pos < n && data[pos] == b)
103     {
104       pos++;
105       return true;
106     }
107   else
108     return false;
109 }
110
111 static void
112 match_byte_assert(uint8_t b, const char *where)
113 {
114   if (!match_byte(b))
115     {
116       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
117       exit(1);
118     }
119 }
120 #define match_byte_assert(b) match_byte_assert(b, WHERE)
121
122 static void
123 newline(FILE *stream, int pos)
124 {
125   fprintf(stream, "\n%08x: ", pos);
126 }
127
128 static void
129 dump_raw(FILE *stream, int start, int end)
130 {
131   for (size_t i = start; i < end; )
132     {
133       if (i + 5 <= n
134           && data[i]
135           //&& !data[i + 1]
136           && !data[i + 2]
137           && !data[i + 3]
138           && i + 4 + data[i] + data[i + 1] * 256 <= end
139           && all_ascii(&data[i + 4], data[i] + data[i + 1] * 256))
140         {
141           newline(stream, i);
142           fprintf(stream, "\"");
143           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stream);
144           fputs("\" ", stream);
145
146           i += 4 + data[i] + data[i + 1] * 256;
147         }
148       else if (i + 12 <= end
149                && data[i + 1] == 40
150                && data[i + 2] == 5
151                && data[i + 3] == 0)
152         {
153           double d;
154
155           memcpy (&d, &data[i + 4], 8);
156           fprintf (stream, "F40.%d(%.*f)", data[i], data[i], d);
157           i += 12;
158           newline (stream, i);
159         }
160       else if (i + 12 <= end
161                && data[i + 1] == 40
162                && data[i + 2] == 31
163                && data[i + 3] == 0)
164         {
165           double d;
166
167           memcpy (&d, &data[i + 4], 8);
168           fprintf (stream, "PCT40.%d(%.*f)", data[i], data[i], d);
169           i += 12;
170           newline(stream, i);
171         }
172       else if (i + 4 <= end
173                && (data[i] && data[i] != 88 && data[i] != 0x41)
174                && !data[i + 1]
175                && !data[i + 2]
176                && !data[i + 3])
177         {
178           fprintf (stream, "i%d ", data[i]);
179           i += 4;
180         }
181       else
182         {
183           fprintf(stream, "%02x ", data[i]);
184           i++;
185         }
186     }
187
188
189 }
190
191 static bool __attribute__((unused))
192 all_utf8(const char *p_)
193 {
194   const uint8_t *p = (const uint8_t *) p_;
195   size_t len = strlen ((char *) p);
196   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
197     {
198       ucs4_t uc;
199
200       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
201       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
202         return false;
203     }
204   return true;
205 }
206
207 static char *
208 get_string(const char *where)
209 {
210   if (1
211       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
212       /*&& all_ascii(&data[pos + 4], data[pos])*/)
213     {
214       int len = data[pos] + data[pos + 1] * 256;
215       char *s = malloc(len + 1);
216
217       memcpy(s, &data[pos + 4], len);
218       s[len] = 0;
219       pos += 4 + len;
220       return s;
221     }
222   else
223     {
224       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
225       exit(1);
226     }
227 }
228 #define get_string() get_string(WHERE)
229
230 static int
231 get_end(void)
232 {
233   int len = get_u32();
234   return pos + len;
235 }
236
237 static void __attribute__((unused))
238 hex_dump(int ofs, int n)
239 {
240   for (int i = 0; i < n; i++)
241     {
242       int c = data[ofs + i];
243 #if 1
244       if (i && !(i % 16))
245         fprintf(stderr, "-");
246       else
247         fprintf(stderr, " ");
248 #endif
249       fprintf(stderr, "%02x", c);
250       //fprintf(stderr, "%c", c >= 32 && c < 127 ? c : '.');
251     }
252   fprintf(stderr, "\n");
253 }
254
255 static char *
256 dump_counted_string(void)
257 {
258   char *s = NULL;
259   int inner_end = get_end();
260   if (pos != inner_end)
261     {
262       if (match_u32(5))
263         {
264           match_u32_assert(0);
265           match_byte_assert(0x58);
266         }
267       else
268         match_u32_assert(0);
269       if (match_byte(0x31))
270         s = get_string();
271       else
272         match_byte_assert(0x58);
273       if (pos != inner_end)
274         {
275           fprintf(stderr, "inner end discrepancy\n");
276           exit(1);
277         }
278     }
279   return s;
280 }
281
282 static void
283 dump_style(FILE *stream)
284 {
285   match_byte(1);
286   match_byte(0);
287   match_byte(0);
288   match_byte(0);
289   match_byte_assert(1);
290   char *fg = get_string();     /* foreground */
291   char *bg = get_string();     /* background */
292   char *font = get_string();     /* font */
293   int size = data[pos];
294   if (!match_byte(14))
295     match_byte_assert(12); /* size? */
296   fprintf(stream, " fgcolor=\"%s\" bgcolor=\"%s\" font=\"%s\" size=\"%d\"",
297           fg, bg, font, size);
298 }
299
300 static char *
301 dump_nested_string(FILE *stream)
302 {
303   char *s = NULL;
304
305   match_byte_assert (0);
306   match_byte_assert (0);
307   int outer_end = get_end();
308   s = dump_counted_string();
309   if (s)
310     fprintf(stream, " \"%s\"", s);
311   if (match_byte(0x31))
312     dump_style(stream);
313   else
314     match_byte_assert(0x58);
315   match_byte_assert(0x58);
316   if (pos != outer_end)
317     {
318       fprintf(stderr, "outer end discrepancy\n");
319       exit(1);
320     }
321
322   return s;
323 }
324
325 static void
326 dump_value_modifier(FILE *stream)
327 {
328   if (match_byte (0x31))
329     {
330       if (match_u32 (0))
331         {
332           fprintf(stream, "<special0");
333           if (match_u32 (1))
334             {
335               /* Corpus frequencies:
336                  124 "a"
337                  12 "b"
338                  8 "a, b"
339
340                  The given text is appended to the cell in a subscript font.
341               */
342               fprintf(stream, " subscript=\"%s\"", get_string());
343             }
344           else
345             match_u32_assert (0);
346
347           if (version == 1)
348             {
349               /* We only have one SPV file for this version (with many
350                  tables). */
351               match_byte(0);
352               if (!match_u32(1))
353                 match_u32_assert(2);
354               match_byte(0);
355               match_byte(0);
356               if (!match_u32(0) && !match_u32(1) && !match_u32(2) && !match_u32(3) && !match_u32(4) && !match_u32(5) && !match_u32(6) && !match_u32(7) && !match_u32(8) && !match_u32(9))
357                 match_u32_assert(10);
358               match_byte(0);
359               match_byte(0);
360               fprintf(stream, "/>\n");
361               return;
362             }
363
364           int outer_end = get_end();
365           
366           /* This counted-string appears to be a template string,
367              e.g. "Design\: [:^1:]1 Within Subjects Design\: [:^1:]2". */
368           char *template = dump_counted_string();
369           if (template)
370             fprintf(stream, " template=\"%s\"", template);
371
372           if (match_byte(0x31))
373             dump_style(stream);
374           else
375             match_byte_assert(0x58);
376           if (match_byte(0x31))
377             {
378               /* Only two SPV files have anything like this, so it's hard to
379                  generalize. */
380               match_u32_assert(0);
381               match_u32_assert(0);
382               match_u32_assert(0);
383               match_u32_assert(0);
384               match_byte_assert(1);
385               match_byte_assert(0);
386               if (!match_byte(8) && !match_byte(1))
387                 match_byte_assert(2);
388               match_byte_assert(0);
389               match_byte_assert(8);
390               match_byte_assert(0);
391               match_byte_assert(10);
392               match_byte_assert(0);
393             }
394           else
395             match_byte_assert(0x58);
396           if (pos != outer_end)
397             {
398               fprintf(stderr, "outer end discrepancy\n");
399               exit(1);
400             }
401           fprintf(stream, "/>\n");
402         }
403       else
404         {
405           int count = get_u32();
406           fprintf(stream, "<footnote-ref indexes=\"");
407           for (int i = 0; i < count; i++)
408             {
409               if (i)
410                 putc(' ', stream);
411               fprintf(stream, "%d", get_u16());
412             }
413           putc('"', stream);
414           match_byte_assert(0);
415           match_byte_assert(0);
416           dump_nested_string(stream);
417           fprintf(stream, "/>\n");
418         }
419     }
420   else
421     match_byte_assert (0x58);
422 }
423
424 static const char *
425 format_to_string (int type)
426 {
427   static char tmp[16];
428   switch (type)
429     {
430     case 1: return "A";
431     case 2: return "AHEX";
432     case 3: return "COMMA";
433     case 4: return "DOLLAR";
434     case 5: case 40: return "F";
435     case 6: return "IB";
436     case 7: return "PIBHEX";
437     case 8: return "P";
438     case 9: return "PIB";
439     case 10: return "PK";
440     case 11: return "RB";
441     case 12: return "RBHEX";
442     case 15: return "Z";
443     case 16: return "N";
444     case 17: return "E";
445     case 20: return "DATE";
446     case 21: return "TIME";
447     case 22: return "DATETIME";
448     case 23: return "ADATE";
449     case 24: return "JDATE";
450     case 25: return "DTIME";
451     case 26: return "WKDAY";
452     case 27: return "MONTH";
453     case 28: return "MOYR";
454     case 29: return "QYR";
455     case 30: return "WKYR";
456     case 31: return "PCT";
457     case 32: return "DOT";
458     case 33: return "CCA";
459     case 34: return "CCB";
460     case 35: return "CCC";
461     case 36: return "CCD";
462     case 37: return "CCE";
463     case 38: return "EDATE";
464     case 39: return "SDATE";
465     default:
466       abort();
467       sprintf(tmp, "<%d>", type);
468       return tmp;
469     }
470 }
471
472 static void
473 dump_value(FILE *stream, int level)
474 {
475   match_byte(0);
476   match_byte(0);
477   match_byte(0);
478   match_byte(0);
479
480   for (int i = 0; i <= level; i++)
481     fprintf (stream, "    ");
482
483   if (match_byte (1))
484     {
485       unsigned int format;
486       double value;
487
488       dump_value_modifier(stream);
489       format = get_u32 ();
490       value = get_double ();
491       fprintf (stream, "<number value=\"%.*g\" format=\"%s%d.%d\"/>\n",
492                DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
493     }
494   else if (match_byte (2))
495     {
496       unsigned int format;
497       char *var, *vallab;
498       double value;
499
500       dump_value_modifier (stream);
501       format = get_u32 ();
502       value = get_double ();
503       var = get_string ();
504       vallab = get_string ();
505       fprintf (stream, "<numeric-datum value=\"%.*g\" format=\"%s%d.%d\"",
506               DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
507       if (var[0])
508         fprintf (stream, " variable=\"%s\"", var);
509       if (vallab[0])
510         fprintf (stream, " label=\"%s\"", vallab);
511       fprintf (stream, "/>\n");
512       if (!match_byte (1) && !match_byte(2))
513         match_byte_assert (3);
514     }
515   else if (match_byte (3))
516     {
517       char *text =  get_string();
518       dump_value_modifier(stream);
519       char *identifier = get_string();
520       char *text_eng = get_string();
521       fprintf (stream, "<string c=\"%s\"", text_eng);
522       if (identifier[0])
523         fprintf (stream, " identifier=\"%s\"", identifier);
524       if (strcmp(text_eng, text))
525         fprintf (stream, " local=\"%s\"", text);
526       fprintf (stream, "/>\n");
527       if (!match_byte (0))
528         match_byte_assert(1);
529     }
530   else if (match_byte (4))
531     {
532       unsigned int format;
533       char *var, *vallab, *value;
534
535       dump_value_modifier(stream);
536       format = get_u32 ();
537       vallab = get_string ();
538       var = get_string ();
539       if (!match_byte(1) && !match_byte(2))
540         match_byte_assert (3);
541       value = get_string ();
542       fprintf (stream, "<string-datum value=\"%s\" format=\"%s%d.%d\"",
543               value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
544       if (var[0])
545         fprintf (stream, " variable=\"%s\"", var);
546       if (vallab[0])
547         fprintf (stream, " label=\"%s\"/>\n", vallab);
548       fprintf (stream, "/>\n");
549     }
550   else if (match_byte (5))
551     {
552       dump_value_modifier(stream);
553       char *name = get_string ();
554       char *label = get_string ();
555       fprintf (stream, "<variable name=\"%s\"", name);
556       if (label[0])
557         fprintf (stream, " label=\"%s\"", label);
558       fprintf (stream, "/>\n");
559       if (!match_byte(1) && !match_byte(2))
560         match_byte_assert(3);
561     }
562   else
563     {
564       dump_value_modifier(stream);
565
566       char *base = get_string();
567       int x = get_u32();
568       fprintf (stream, "<template format=\"%s\">\n", base);
569       for (int i = 0; i < x; i++)
570         {
571           int y = get_u32();
572           if (!y)
573             y = 1;
574           else
575             match_u32_assert(0);
576           for (int j = 0; j <= level + 1; j++)
577             fprintf (stream, "    ");
578           fprintf (stream, "<substitution index=\"%d\">\n", i + 1);
579           for (int j = 0; j < y; j++)
580             dump_value (stream, level + 2);
581           for (int j = 0; j <= level + 1; j++)
582             fprintf (stream, "    ");
583           fprintf (stream, "</substitution>\n");
584         }
585       for (int j = 0; j <= level; j++)
586         fprintf (stream, "    ");
587       fprintf (stream, "</template>\n");
588     }
589 }
590
591 static int
592 compare_int(const void *a_, const void *b_)
593 {
594   const int *a = a_;
595   const int *b = b_;
596   return *a < *b ? -1 : *a > *b;
597 }
598
599 static void
600 check_permutation(int *a, int n, const char *name)
601 {
602   int b[n];
603   memcpy(b, a, n * sizeof *a);
604   qsort(b, n, sizeof *b, compare_int);
605   for (int i = 0; i < n; i++)
606     if (b[i] != i)
607       {
608         fprintf(stderr, "bad %s permutation:", name);
609         for (int i = 0; i < n; i++)
610           fprintf(stderr, " %d", a[i]);
611         putc('\n', stderr);
612         exit(1);
613       }
614 }
615
616 static void
617 dump_category(FILE *stream, int level, int **indexes, int *allocated_indexes,
618               int *n_indexes)
619 {
620   for (int i = 0; i <= level; i++)
621     fprintf (stream, "    ");
622   printf ("<category>\n");
623   dump_value (stream, level + 1);
624
625   int merge = data[pos];
626   if (!match_byte(0))
627     match_byte_assert (1);
628
629   match_byte_assert (0);
630
631   int unindexed = data[pos];
632   if (!match_byte(0))
633     match_byte_assert (1);
634
635   int x = get_u32 ();
636   pos -= 4;
637   if (!match_u32 (0))
638     match_u32_assert (2);
639
640   int indx = get_u32();
641   int n_categories = get_u32();
642   if (indx == -1)
643     {
644       if (merge)
645         {
646           for (int i = 0; i <= level + 1; i++)
647             fprintf (stream, "    ");
648           fprintf (stream, "<merge/>\n");
649         }
650     }
651   else
652     {
653       if (merge)
654         {
655           fprintf(stderr, "index not -1 but merged\n");
656           exit(1);
657         }
658       if (x != 2)
659         {
660           fprintf(stderr, "index not -1 but x != 2\n");
661           exit(1);
662         }
663       if (n_categories != 0)
664         {
665           fprintf(stderr, "index not -1 but subcategories\n");
666           exit(1);
667         }
668       if (*n_indexes >= *allocated_indexes)
669         {
670           *allocated_indexes = *allocated_indexes ? 2 * *allocated_indexes : 16;
671           *indexes = realloc(*indexes, *allocated_indexes * sizeof **indexes);
672         }
673       (*indexes)[(*n_indexes)++] = indx;
674     }
675
676   int expected_unindexed = indx == -1;
677   if (unindexed != expected_unindexed)
678     {
679       fprintf(stderr, "unindexed (%d) mismatch with indx (%d)\n",
680               unindexed, indx);
681       exit(1);
682     }
683
684   if (n_categories == 0)
685     {
686       for (int i = 0; i <= level + 1; i++)
687         fprintf (stream, "    ");
688       fprintf (stream, "<category-index>%d</category-index>\n", indx);
689     }
690   for (int i = 0; i < n_categories; i++)
691     dump_category (stream, level + 1, indexes, allocated_indexes, n_indexes);
692   for (int i = 0; i <= level; i++)
693     fprintf (stream, "    ");
694   printf ("</category>\n");
695 }
696
697 static int
698 dump_dim(int indx)
699 {
700   int n_categories;
701
702   printf ("<dimension index=\"%d\">\n", indx);
703   dump_value (stdout, 0);
704
705   /* This byte is usually 0 but many other values have been spotted. */
706   pos++;
707
708   if (!match_byte(0) && !match_byte(1))
709     match_byte_assert(2);
710   if (!match_u32(0))
711     match_u32_assert(2);
712   if (!match_byte(0))
713     match_byte_assert(1);
714   if (!match_byte(0))
715     match_byte_assert(1);
716   match_byte_assert(1);
717   if (!match_u32(UINT32_MAX))
718     match_u32_assert(indx);
719   n_categories = get_u32();
720
721   int *indexes = NULL;
722   int n_indexes = 0;
723   int allocated_indexes = 0;
724   for (int i = 0; i < n_categories; i++)
725     dump_category (stdout, 0, &indexes, &allocated_indexes, &n_indexes);
726   check_permutation(indexes, n_indexes, "categories");
727
728   fprintf (stdout, "</dimension>\n");
729   return n_indexes;
730 }
731
732 int n_dims;
733 static int dim_n_cats[64];
734 #define MAX_DIMS (sizeof dim_n_cats / sizeof *dim_n_cats)
735
736 static void
737 dump_dims(void)
738 {
739   n_dims = get_u32();
740   assert(n_dims < MAX_DIMS);
741   for (int i = 0; i < n_dims; i++)
742     dim_n_cats[i] = dump_dim (i);
743 }
744
745 static void
746 dump_data(void)
747 {
748   /* The first three numbers add to the number of dimensions. */
749   int l = get_u32();
750   int r = get_u32();
751   int c = n_dims - l - r;
752   match_u32_assert(c);
753
754   /* The next n_dims numbers are a permutation of the dimension numbers. */
755   int a[n_dims];
756   for (int i = 0; i < n_dims; i++)
757     {
758       int dim = get_u32();
759       a[i] = dim;
760
761       const char *name = i < l ? "layer" : i < l + r ? "row" : "column";
762       printf ("<%s dimension=\"%d\"/>\n", name, dim);
763     }
764   check_permutation(a, n_dims, "dimensions");
765
766   int x = get_u32();
767   printf ("<data>\n");
768   for (int i = 0; i < x; i++)
769     {
770       unsigned int indx = get_u32();
771       printf ("    <datum index=\"%d\" coords=", indx);
772
773       int coords[MAX_DIMS];
774       for (int i = n_dims; i-- > 0; )
775         {
776           coords[i] = indx % dim_n_cats[i];
777           indx /= dim_n_cats[i];
778         }
779       for (int i = 0; i < n_dims; i++)
780         printf("%c%d", i ? ',' : '"', coords[i]);
781
782       printf ("\">\n");
783       match_u32_assert(0);
784       if (version == 1)
785         match_byte(0);
786       dump_value(stdout, 1);
787       fprintf (stdout, "    </datum>\n");
788     }
789   printf ("</data>\n");
790 }
791
792 static void
793 dump_title(void)
794 {
795   printf ("<title-local>\n");
796   dump_value(stdout, 0);
797   match_byte(1);
798   printf ("</title-local>\n");
799
800   printf ("<subtype>\n");
801   dump_value(stdout, 0);
802   match_byte(1);
803   printf ("</subtype>\n");
804
805   match_byte_assert(0x31);
806
807   printf ("<title-c>\n");
808   dump_value(stdout, 0);
809   match_byte(1);
810   printf ("</title-c>\n");
811
812   if (match_byte(0x31))
813     {
814       printf ("<user-caption>\n");
815       dump_value(stdout, 0);
816       printf ("</user-caption>\n");
817     }
818   else
819     match_byte_assert(0x58);
820   if (match_byte(0x31))
821     {
822       printf ("<caption>\n");
823       dump_value(stdout, 0);
824       printf ("</caption>\n");
825     }
826   else
827     match_byte_assert(0x58);
828
829   int n_footnotes = get_u32();
830   for (int i = 0; i < n_footnotes; i++)
831     {
832       printf ("<footnote index=\"%d\">\n", i);
833       dump_value(stdout, 0);
834       /* Custom footnote marker string. */
835       if (match_byte (0x31))
836         dump_value(stdout, 0);
837       else
838         match_byte_assert (0x58);
839       get_u32 ();
840       printf ("</footnote>\n");
841     }
842 }
843
844 static void
845 dump_fonts(void)
846 {
847   match_byte(0);
848   for (int i = 1; i <= 8; i++)
849     {
850       printf ("<style index=\"%d\"", i);
851       match_byte_assert(i);
852       match_byte_assert(0x31);
853       printf(" font=\"%s\"", get_string());
854       match_byte_assert(0);
855       match_byte_assert(0);
856       if (!match_byte(0x40) && !match_byte(0x20) && !match_byte(0x80) && !match_byte(0x10) && !match_byte(0x70))
857         match_byte_assert(0x50);
858       match_byte_assert(0x41);
859       if (!match_u32(0) && !match_u32(1))
860         match_u32_assert(2);
861       match_byte_assert(0);
862
863       /* OK, this seems really unlikely to be totally correct, but it matches my corpus... */
864       if (!match_u32(0) && !match_u32(2) && !match_u32(4))
865         {
866           if (i == 7)
867             match_u32_assert(0xfaad);
868           else
869             match_u32_assert(0);
870         }
871
872       if (!match_u32(0) && !match_u32(1) && !match_u32(2))
873         match_u32_assert(3);
874       printf (" fgcolor=\"%s\"", get_string());
875       printf (" bgcolor=\"%s\"", get_string());
876       if (!match_byte(0))
877         match_byte_assert(1);
878       match_u32_assert(0);
879       char *othercolor = get_string();
880       if (othercolor[0])
881         printf(" othercolor=\"%s\"", othercolor);
882
883       if (version > 1)
884         {
885           if (i != 3)
886             {
887               pos += 12;
888             }
889           else
890             {
891               get_u32();
892               if (!match_u32(-1) && !match_u32(8))
893                 match_u32_assert(24);
894               if (!match_u32(-1) && !match_u32(2))
895                 match_u32_assert(3);
896             }
897
898           /* Who knows? Ranges from -1 to 8 with no obvious pattern. */
899           get_u32();
900         }
901
902       printf ("/>\n");
903     }
904
905   match_u32_assert(240);
906   pos += 240;
907
908   int skip = get_u32();
909   assert(skip == 18 || skip == 25);
910   pos += skip;
911
912   int x3 = get_u32();
913   if (version == 3)
914     {
915       assert(x3 >= 117);
916       int len = data[pos + 0x34];
917       if (len)
918         printf("<tablelook>%.*s</tablelook>\n", len, &data[pos + 0x35]);
919     }
920   pos += x3;
921
922   int count = get_u32();
923   pos += 4 * count;
924
925   const char *locale = get_string();
926   printf ("<locale>%s</locale>\n", locale);
927
928   get_u32();            /* Seen: 0, UINT32_MAX, 2, 3, 4, 5, 6, 8, 9, 21, 24. */
929   if (!match_byte(0))
930     match_byte_assert(1);
931   match_byte_assert(0);
932   if (!match_byte(0))
933     match_byte_assert(1);
934   if (version > 1)
935     {
936       if (!match_byte(0x97) && !match_byte(0x98)
937           && !match_byte(0x99) && !match_byte(0x9a) && !match_byte(0x9b))
938         match_byte_assert(0x9c);
939       match_byte_assert(7);
940       match_byte_assert(0);
941       match_byte_assert(0);
942     }
943   else
944     {
945       printf("%x\n", get_u32());
946     }
947
948   int decimal = data[pos];
949   int grouping = data[pos + 1];
950   if (match_byte('.'))
951     {
952       if (!match_byte(',') && !match_byte('\''))
953         match_byte_assert(' ');
954     }
955   else
956     {
957       match_byte_assert(',');
958       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
959         match_byte_assert(0);
960     }
961   printf("<format decimal=\"%c\" grouping=\"", decimal);
962   if (grouping)
963     putchar(grouping);
964   printf("\"/>\n");
965   if (match_u32(5))
966     {
967       for (int i = 0; i < 5; i++)
968         printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
969     }
970   else
971     match_u32_assert(0);
972
973   /* The last chunk is an outer envelope that contains two inner envelopes.
974      The second inner envelope has some interesting data like the encoding and
975      the locale. */
976   if (version == 3)
977     {
978       int outer_end = get_end();
979
980       /* First inner envelope: byte*33 int[n] int*[n]. */
981       pos = get_end();
982
983       /* Second inner envelope. */
984       assert(get_end() == outer_end);
985
986       match_byte_assert(1);
987       match_byte_assert(0);
988       if (!match_byte(3) && !match_byte(4))
989         match_byte_assert(5);
990       match_byte_assert(0);
991       match_byte_assert(0);
992       match_byte_assert(0);
993
994       printf("<command>%s</command>\n", get_string());
995       printf("<subcommand>%s</subcommand>\n", get_string());
996       printf("<language>%s</language>\n", get_string());
997       printf("<charset>%s</charset>\n", get_string());
998       printf("<locale>%s</locale>\n", get_string());
999
1000       if (!match_byte(0))
1001         match_byte_assert(1);
1002       match_byte_assert(0);
1003       if (!match_byte(0))
1004         match_byte_assert(1);
1005       if (!match_byte(0))
1006         match_byte_assert(1);
1007
1008       if (!match_byte(0x97) && !match_byte(0x98)
1009           && !match_byte(0x99) && !match_byte(0x9a) && !match_byte(0x9b))
1010         match_byte_assert(0x9c);
1011       match_byte_assert(7);
1012       match_byte_assert(0);
1013       match_byte_assert(0);
1014
1015       if (match_byte('.'))
1016         {
1017           if (!match_byte(',') && !match_byte('\''))
1018             match_byte_assert(' ');
1019         }
1020       else
1021         {
1022           match_byte_assert(',');
1023           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1024             match_byte_assert(0);
1025         }
1026
1027       pos += 8;
1028       match_byte_assert(1);
1029
1030       if (outer_end - pos > 6)
1031         {
1032           /* There might be a pair of strings representing a dataset and
1033              datafile name, or there might be a set of custom currency strings.
1034              The custom currency strings start with a pair of integers, so we
1035              can distinguish these from a string by checking for a null byte; a
1036              small 32-bit integer will always contain a null and a text string
1037              never will. */
1038           int save_pos = pos;
1039           int len = get_u32();
1040           bool has_dataset = !memchr(&data[pos], '\0', len);
1041           pos = save_pos;
1042
1043           if (has_dataset)
1044             {
1045               printf("<dataset>%s</dataset>\n", get_string());
1046               printf("<datafile>%s</datafile>\n", get_string());
1047
1048               match_u32_assert(0);
1049               get_u32();
1050               match_u32_assert(0);
1051             }
1052         }
1053
1054       if (match_u32(5))
1055         {
1056           for (int i = 0; i < 5; i++)
1057             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1058         }
1059       else
1060         match_u32_assert(0);
1061
1062       match_byte_assert(0x2e);
1063       if (!match_byte(0))
1064         match_byte_assert(1);
1065
1066       if (pos < outer_end)
1067         {
1068           printf("<seed>%d</seed>\n", get_u32());
1069           match_u32_assert(0);
1070         }
1071       assert(pos == outer_end);
1072
1073       pos = outer_end;
1074     }
1075   else
1076     {
1077       pos = get_end();
1078     }
1079 }
1080
1081 int
1082 main(int argc, char *argv[])
1083 {
1084   size_t start;
1085   struct stat s;
1086
1087   if (isatty(STDIN_FILENO))
1088     {
1089       fprintf(stderr, "redirect stdin from a .bin file\n");
1090       exit(1);
1091     }
1092   if (fstat(STDIN_FILENO, &s))
1093     {
1094       perror("fstat");
1095       exit(1);
1096     }
1097   n = s.st_size;
1098   data = malloc(n);
1099   if (!data)
1100     {
1101       perror("malloc");
1102       exit(1);
1103     }
1104   if (read(STDIN_FILENO, data, n) != n)
1105     {
1106       perror("read");
1107       exit(1);
1108     }
1109
1110   if (argc != 2)
1111     {
1112       fprintf (stderr, "usage: %s TYPE < .bin", argv[0]);
1113       exit (1);
1114     }
1115
1116   if (!strcmp(argv[1], "title0"))
1117     {
1118       pos = 0x27;
1119       if (match_byte (0x03)
1120           || (match_byte (0x05) && match_byte (0x58)))
1121         printf ("%s\n", get_string());
1122       else
1123         printf ("<unknown>\n");
1124       return 0;
1125     }
1126   else if (!strcmp(argv[1], "title"))
1127     {
1128       pos = 0x27;
1129       dump_title();
1130       exit(0);
1131     }
1132   else if (!strcmp(argv[1], "titleraw"))
1133     {
1134       const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
1135       start = 0x27;
1136       n = find(fonts, sizeof fonts - 1);
1137     }
1138   else if (!strcmp(argv[1], "fonts"))
1139     {
1140       const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
1141       const char styles[] = "\xf0\0\0\0";
1142       start = find(fonts, sizeof fonts - 1);
1143       n = find(styles, sizeof styles - 1);
1144     }
1145   else if (!strcmp(argv[1], "styles"))
1146     {
1147       const char styles[] = "\xf0\0\0\0";
1148       const char dimensions[] = "-,,,.\0";
1149       start = find(styles, sizeof styles - 1);
1150       n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
1151     }
1152   else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
1153     {
1154       pos = 0;
1155       match_byte_assert(1);
1156       match_byte_assert(0);
1157
1158       /* This might be a version number of some kind, because value 1 seems
1159          to only appear in an SPV file that also required its own weird
1160          special cases in dump_value_modifier(). */
1161       version = get_u32();
1162       pos -= 4;
1163       if (!match_u32(1))
1164         match_u32_assert(3);
1165
1166       match_byte_assert(1);
1167       if (!match_byte(0))
1168         match_byte_assert(1);
1169
1170       /* Offset 8. */
1171       match_byte_assert(0);
1172       if (!match_byte(0))
1173         match_byte_assert(1);
1174       if (!match_byte(0))
1175         match_byte_assert(1);
1176
1177       /* Offset 11. */
1178       pos++;
1179       match_byte_assert(0);
1180       match_byte_assert(0);
1181       match_byte_assert(0);
1182
1183       /* Offset 15. */
1184       pos++;
1185       if (!match_byte(0))
1186         match_byte_assert(1);
1187       match_byte_assert(0);
1188       match_byte_assert(0);
1189
1190       /* Offset 19. */
1191       pos++;
1192       if (!match_byte(0))
1193         match_byte_assert(1);
1194       match_byte_assert(0);
1195       match_byte_assert(0);
1196
1197       /* Offset 23. */
1198       pos++;
1199       if (!match_byte(0))
1200         match_byte_assert(1);
1201       match_byte_assert(0);
1202       match_byte_assert(0);
1203
1204       /* Offset 27. */
1205       pos++;
1206       pos++;
1207       pos++;
1208       pos++;
1209
1210       /* Offset 31.
1211
1212          This is the tableId, e.g. -4154297861994971133 would be 0xdca00003.
1213          We don't have enough context to validate it. */
1214       pos += 4;
1215
1216       /* Offset 35. */
1217       pos += 4;
1218
1219       dump_title ();
1220       dump_fonts();
1221       dump_dims ();
1222       dump_data ();
1223       match_byte (1);
1224       if (pos != n)
1225         {
1226           fprintf (stderr, "%x / %x\n", pos, n);
1227           exit(1);
1228         }
1229       exit(0);
1230     }
1231   else if (!strcmp(argv[1], "raw"))
1232     {
1233       start = 0x27;
1234
1235       dump_raw(stdout, start, n);
1236     }
1237   else
1238     {
1239       fprintf (stderr, "unknown section %s\n", argv[1]);
1240       exit(1);
1241     }
1242
1243   return 0;
1244 }