7acd63a82858ab0ac7adc04760bbb117411d83ab
[ia32rtools.git] / tools / protoparse.h
1 /*
2  * ia32rtools
3  * (C) notaz, 2013,2014
4  *
5  * This work is licensed under the terms of 3-clause BSD license.
6  * See COPYING file in the top-level directory.
7  */
8
9 struct parsed_proto;
10
11 struct parsed_type {
12         char *name;
13         unsigned int is_array:1;
14         unsigned int is_ptr:1;
15         unsigned int is_struct:1; // split for args
16         unsigned int is_retreg:1; // register to return to caller
17         unsigned int is_va_list:1;
18         unsigned int is_64bit:1;
19         unsigned int is_float:1;  // float, double
20 };
21
22 struct parsed_proto_arg {
23         char *reg;
24         struct parsed_type type;
25         struct parsed_proto *pp; // fptr or struct
26         void *datap;
27         unsigned int is_saved:1; // not set here, for tool use
28 };
29
30 struct parsed_proto {
31         char name[256];
32         union {
33                 struct parsed_type ret_type;
34                 struct parsed_type type;
35         };
36         struct parsed_proto_arg arg[16];
37         int argc;
38         int argc_stack;
39         int argc_reg;
40         unsigned int is_func:1;
41         unsigned int is_stdcall:1;
42         unsigned int is_fastcall:1;
43         unsigned int is_vararg:1;     // vararg func
44         unsigned int is_fptr:1;
45         unsigned int is_import:1;     // data import
46         unsigned int is_noreturn:1;
47         unsigned int is_unresolved:1;
48         unsigned int is_guessed:1;    // for extra checking
49         unsigned int is_userstack:1;
50         unsigned int is_include:1;    // not from top-level header
51         unsigned int is_osinc:1;      // OS/system library func
52         unsigned int is_cinc:1;       // crt library func
53         unsigned int is_arg:1;        // declared in some func arg
54         unsigned int has_structarg:1;
55         unsigned int has_retreg:1;
56 };
57
58 struct parsed_struct {
59         char name[256];
60         struct {
61                 int offset;
62                 struct parsed_proto pp;
63         } members[64];
64         int member_count;
65 };
66
67 static const char *hdrfn;
68 static int hdrfline = 0;
69
70 static void pp_copy_arg(struct parsed_proto_arg *d,
71         const struct parsed_proto_arg *s);
72
73 static int b_pp_c_handler(char *proto, const char *fname,
74         int is_include, int is_osinc, int is_cinc);
75 static int struct_handler(FILE *fhdr, char *proto, int *line);
76
77 static int do_protostrs(FILE *fhdr, const char *fname, int is_include)
78 {
79         const char *finc_name;
80         const char *hdrfn_saved;
81         char protostr[256];
82         char path[256];
83         char fname_inc[256];
84         int is_osinc;
85         int is_cinc;
86         FILE *finc;
87         int line = 0;
88         int ret;
89         char *p;
90
91         hdrfn_saved = hdrfn;
92         hdrfn = fname;
93
94         is_cinc = strstr(fname, "stdc.hlist") != NULL;
95         is_osinc = is_cinc || strstr(fname, "win32.hlist") != NULL;
96
97         while (fgets(protostr, sizeof(protostr), fhdr))
98         {
99                 line++;
100                 if (strncmp(protostr, "//#include ", 11) == 0) {
101                         finc_name = protostr + 11;
102                         p = strpbrk(finc_name, "\r\n ");
103                         if (p != NULL)
104                                 *p = 0;
105
106                         path[0] = 0;
107                         p = strrchr(hdrfn_saved, '/');
108                         if (p) {
109                                 memcpy(path, hdrfn_saved,
110                                         p - hdrfn_saved + 1);
111                                 path[p - hdrfn_saved + 1] = 0;
112                         }
113                         snprintf(fname_inc, sizeof(fname_inc), "%s%s", 
114                                 path, finc_name);
115                         finc = fopen(fname_inc, "r");
116                         if (finc == NULL) {
117                                 printf("%s:%d: can't open '%s'\n",
118                                         fname_inc, line, finc_name);
119                                 continue;
120                         }
121                         ret = do_protostrs(finc, finc_name, 1);
122                         fclose(finc);
123                         if (ret < 0)
124                                 break;
125                         continue;
126                 }
127                 if (strncmp(sskip(protostr), "//", 2) == 0)
128                         continue;
129
130                 p = protostr + strlen(protostr);
131                 for (p--; p >= protostr && my_isblank(*p); --p)
132                         *p = 0;
133                 if (p < protostr)
134                         continue;
135
136                 hdrfline = line;
137
138                 if (!strncmp(protostr, "struct", 6)
139                     && strchr(protostr, '{') != NULL)
140                         ret = struct_handler(fhdr, protostr, &line);
141                 else
142                         ret = b_pp_c_handler(protostr, hdrfn,
143                                 is_include, is_osinc, is_cinc);
144                 if (ret < 0)
145                         break;
146         }
147
148         hdrfn = hdrfn_saved;
149
150         if (feof(fhdr))
151                 return 0;
152
153         return -1;
154 }
155
156 static int get_regparm(char *dst, size_t dlen, char *p, int *retreg)
157 {
158         int i = 0, o;
159
160         *retreg = 0;
161
162         if (*p != '<')
163                 return 0;
164
165         i++;
166         if (p[i] == '*') {
167                 *retreg = 1;
168                 i++;
169         }
170
171         for (o = 0; o < dlen; i++) {
172                 if (p[i] == 0)
173                         return 0;
174                 if (p[i] == '>')
175                         break;
176                 dst[o++] = p[i];
177         }
178         dst[o] = 0;
179         return i + 1;
180 }
181
182 // hmh..
183 static const char *known_type_mod[] = {
184         "const",
185         "signed",
186         "unsigned",
187         "enum",
188         "CONST",
189         "volatile",
190 };
191
192 static const char *known_ptr_types[] = {
193         "FARPROC",
194         "WNDPROC",
195         "LINECALLBACK",
196         "HACCEL",
197         "HANDLE",
198         "HBITMAP",
199         "HBRUSH",
200         "HCALL",
201         "HCURSOR",
202         "HDC",
203         "HFONT",
204         "HGDIOBJ",
205         "HGLOBAL",
206         "HHOOK",
207         "HICON",
208         "HINSTANCE",
209         "HIMC", // DWORD in mingw, ptr in wine..
210         "HLINE",
211         "HLINEAPP",
212         "HLOCAL",
213         "HMODULE",
214         "HPALETTE",
215         "HRGN",
216         "HRSRC",
217         "HKEY",
218         "HMENU",
219         "HWAVEOUT",
220         "HWND",
221         "PAPPBARDATA",
222         "PBYTE",
223         "PCRITICAL_SECTION",
224         "PDEVMODEA",
225         "PDWORD",
226         "PFILETIME",
227         "PLARGE_INTEGER",
228         "PHANDLE",
229         "PHKEY",
230         "PLONG",
231         "PMEMORY_BASIC_INFORMATION",
232         "PUINT",
233         "PULARGE_INTEGER",
234         "PULONG_PTR",
235         "PVOID",
236         "PCVOID",
237         "PWORD",
238         "REFCLSID",
239         "REFGUID",
240         "REFIID",
241         "HOOKPROC",
242         "DLGPROC",
243         "TIMERPROC",
244         "WNDENUMPROC",
245         "va_list",
246         "__VALIST",
247 };
248
249 static const char *ignored_keywords[] = {
250         "extern",
251         "WINBASEAPI",
252         "WINUSERAPI",
253         "WINGDIAPI",
254         "WINADVAPI",
255 };
256
257 static int typecmp(const char *n, const char *t)
258 {
259         for (; *t != 0; n++, t++) {
260                 while (n[0] == ' ' && (n[1] == ' ' || n[1] == '*'))
261                         n++;
262                 while (t[0] == ' ' && (t[1] == ' ' || t[1] == '*'))
263                         t++;
264                 if (*n != *t)
265                         return *n - *t;
266         }
267
268         return 0;
269 }
270
271 static const char *skip_type_mod(const char *n)
272 {
273         int len;
274         int i;
275
276         for (i = 0; i < ARRAY_SIZE(known_type_mod); i++) {
277                 len = strlen(known_type_mod[i]);
278                 if (strncmp(n, known_type_mod[i], len) != 0)
279                         continue;
280                 if (!my_isblank(n[len]))
281                         continue;
282
283                 n += len;
284                 while (my_isblank(*n))
285                         n++;
286                 i = 0;
287         }
288
289         return n;
290 }
291
292 static int check_type(const char *name, struct parsed_type *type)
293 {
294         const char *n, *n1;
295         int ret = -1;
296         int i;
297
298         n = skip_type_mod(name);
299
300         if (!strncmp(n, "struct", 6) && my_isblank(n[6])) {
301                 type->is_struct = 1;
302
303                 n += 6;
304                 while (my_isblank(*n))
305                         n++;
306         }
307
308         for (i = 0; i < ARRAY_SIZE(known_ptr_types); i++) {
309                 if (typecmp(n, known_ptr_types[i]))
310                         continue;
311
312                 type->is_ptr = 1;
313                 break;
314         }
315
316         if (n[0] == 'L' && n[1] == 'P' && strncmp(n, "LPARAM", 6))
317                 type->is_ptr = 1;
318
319         // assume single word
320         while (!my_isblank(*n) && !my_issep(*n))
321                 n++;
322
323         while (1) {
324                 n1 = n;
325                 while (my_isblank(*n))
326                         n++;
327                 if (*n == '*') {
328                         type->is_ptr = 1;
329                         n++;
330                         continue;
331                 }
332                 break;
333         }
334
335         ret = n1 - name;
336         type->name = strndup(name, ret);
337         if (IS(type->name, "__VALIST") || IS(type->name, "va_list"))
338                 type->is_va_list = 1;
339         if (IS(type->name, "VOID"))
340                 memcpy(type->name, "void", 4);
341
342         return ret;
343 }
344
345 /* args are always expanded to 32bit */
346 static const char *map_reg(const char *reg)
347 {
348         const char *regs_f[] = { "eax", "ebx", "ecx", "edx", "esi", "edi" };
349         const char *regs_w[] = { "ax",  "bx",  "cx",  "dx",  "si",  "di" };
350         const char *regs_b[] = { "al",  "bl",  "cl",  "dl" };
351         int i;
352
353         for (i = 0; i < ARRAY_SIZE(regs_w); i++)
354                 if (IS(reg, regs_w[i]))
355                         return regs_f[i];
356
357         for (i = 0; i < ARRAY_SIZE(regs_b); i++)
358                 if (IS(reg, regs_b[i]))
359                         return regs_f[i];
360
361         return reg;
362 }
363
364 static int check_struct_arg(struct parsed_proto_arg *arg)
365 {
366         if (IS(arg->type.name, "POINT"))
367                 return 2 - 1;
368
369         return 0;
370 }
371
372 static int parse_protostr(char *protostr, struct parsed_proto *pp);
373
374 static int parse_arg(char **p_, struct parsed_proto_arg *arg, int xarg)
375 {
376         char buf[256];
377         char *p = *p_;
378         char *pe;
379         int ret;
380
381         arg->pp = calloc(1, sizeof(*arg->pp));
382         my_assert_not(arg->pp, NULL);
383         arg->pp->is_arg = 1;
384
385         pe = p;
386         while (1) {
387                 pe = strpbrk(pe, ",()");
388                 if (pe == NULL)
389                         return -1;
390                 if (*pe == ',' || *pe == ')')
391                         break;
392                 pe = strchr(pe, ')');
393                 if (pe == NULL)
394                         return -1;
395                 pe++;
396         }
397
398         if (pe - p > sizeof(buf) - 1)
399                 return -1;
400         memcpy(buf, p, pe - p);
401         buf[pe - p] = 0;
402
403         ret = parse_protostr(buf, arg->pp);
404         if (ret < 0)
405                 return -1;
406
407         if (IS_START(arg->pp->name, "guess"))
408                 arg->pp->is_guessed = 1;
409
410         // we don't use actual names right now...
411         snprintf(arg->pp->name, sizeof(arg->pp->name), "a%d", xarg);
412
413         if (!arg->type.is_struct)
414                 // we'll treat it as void * for non-calls
415                 arg->type.name = strdup("void *");
416         arg->type.is_ptr = 1;
417
418         p += ret;
419         *p_ = p;
420         return 0;
421 }
422
423 static int parse_protostr(char *protostr, struct parsed_proto *pp)
424 {
425         struct parsed_proto_arg *arg;
426         char regparm[16];
427         char buf[256];
428         char cconv[32];
429         int is_retreg;
430         char *p, *p1;
431         int xarg = 0;
432         int i, l;
433         int ret;
434
435         p = sskip(protostr);
436         if (p[0] == '/' && p[1] == '/') {
437                 printf("%s:%d: commented out?\n", hdrfn, hdrfline);
438                 p = sskip(p + 2);
439         }
440
441         // allow start of line comment
442         if (p[0] == '/' && p[1] == '*') {
443                 p = strstr(p + 2, "*/");
444                 if (p == NULL) {
445                         printf("%s:%d: multiline comments unsupported\n",
446                                 hdrfn, hdrfline);
447                         return -1;
448                 }
449                 p = sskip(p + 2);
450         }
451
452         // we need remaining hints in comments, so strip / *
453         for (p1 = p; p1[0] != 0 && p1[1] != 0; p1++) {
454                 if ((p1[0] == '/' && p1[1] == '*')
455                  || (p1[0] == '*' && p1[1] == '/'))
456                         p1[0] = p1[1] = ' ';
457         }
458
459         if (!strncmp(p, "DECLSPEC_NORETURN ", 18)) {
460                 pp->is_noreturn = 1;
461                 p = sskip(p + 18);
462         }
463
464         for (i = 0; i < ARRAY_SIZE(ignored_keywords); i++) {
465                 l = strlen(ignored_keywords[i]);
466                 if (!strncmp(p, ignored_keywords[i], l) && my_isblank(p[l]))
467                         p = sskip(p + l + 1);
468         }
469
470         if (IS_START(p, "DECL_IMPORT ")) {
471                 pp->is_import = 1;
472                 p = sskip(p + 12);
473         }
474
475         ret = check_type(p, &pp->ret_type);
476         if (ret <= 0) {
477                 printf("%s:%d:%zd: unhandled return in '%s'\n",
478                         hdrfn, hdrfline, (p - protostr) + 1, protostr);
479                 return -1;
480         }
481         p = sskip(p + ret);
482
483         if (!strncmp(p, "noreturn ", 9)) {
484                 pp->is_noreturn = 1;
485                 p = sskip(p + 9);
486         }
487
488         if (!strchr(p, ')')) {
489                 p = next_idt(buf, sizeof(buf), p);
490                 p = sskip(p);
491                 if (!pp->is_arg && buf[0] == 0) {
492                         printf("%s:%d:%zd: var name is missing\n",
493                                 hdrfn, hdrfline, (p - protostr) + 1);
494                         return -1;
495                 }
496                 strcpy(pp->name, buf);
497
498                 p1 = strchr(p, ']');
499                 if (p1 != NULL) {
500                         p = p1 + 1;
501                         pp->ret_type.is_array = 1;
502                 }
503                 return p - protostr;
504         }
505
506         pp->is_func = 1;
507
508         if (*p == '(') {
509                 pp->is_fptr = 1;
510                 p = sskip(p + 1);
511         }
512
513         p = next_word(cconv, sizeof(cconv), p);
514         p = sskip(p);
515         if (cconv[0] == 0) {
516                 printf("%s:%d:%zd: cconv missing\n",
517                         hdrfn, hdrfline, (p - protostr) + 1);
518                 return -1;
519         }
520         if      (IS(cconv, "__cdecl"))
521                 pp->is_stdcall = 0;
522         else if (IS(cconv, "__stdcall"))
523                 pp->is_stdcall = 1;
524         else if (IS(cconv, "__fastcall")) {
525                 pp->is_fastcall = 1;
526                 pp->is_stdcall = 1; // sort of..
527         }
528         else if (IS(cconv, "__thiscall"))
529                 pp->is_stdcall = 1;
530         else if (IS(cconv, "__userpurge"))
531                 pp->is_stdcall = 1; // IDA
532         else if (IS(cconv, "__usercall"))
533                 pp->is_stdcall = 0; // IDA
534         else if (IS(cconv, "__userstack")) {
535                 pp->is_stdcall = 0; // custom
536                 pp->is_userstack = 1;
537         }
538         else if (IS(cconv, "WINAPI") || IS(cconv, "PASCAL"))
539                 pp->is_stdcall = 1;
540         else {
541                 printf("%s:%d:%zd: unhandled cconv: '%s'\n",
542                         hdrfn, hdrfline, (p - protostr) + 1, cconv);
543                 return -1;
544         }
545
546         if (pp->is_fptr) {
547                 if (*p != '*') {
548                         printf("%s:%d:%zd: '*' expected\n",
549                                 hdrfn, hdrfline, (p - protostr) + 1);
550                         return -1;
551                 }
552                 p++;
553                 // XXX: skipping extra asterisks, for now
554                 while (*p == '*')
555                         p++;
556                 p = sskip(p);
557         }
558
559         p = next_idt(buf, sizeof(buf), p);
560         p = sskip(p);
561         if (buf[0] == 0) {
562                 //printf("%s:%d:%zd: func name missing\n",
563                 //      hdrfn, hdrfline, (p - protostr) + 1);
564                 //return -1;
565         }
566         strcpy(pp->name, buf);
567
568         ret = get_regparm(regparm, sizeof(regparm), p, &is_retreg);
569         if (ret > 0) {
570                 if (!IS(regparm, "eax") && !IS(regparm, "ax")
571                  && !IS(regparm, "al") && !IS(regparm, "edx:eax"))
572                 {
573                         printf("%s:%d:%zd: bad regparm: %s\n",
574                                 hdrfn, hdrfline, (p - protostr) + 1, regparm);
575                         return -1;
576                 }
577                 p += ret;
578                 p = sskip(p);
579         }
580
581         if (pp->is_fptr) {
582                 if (*p == '[') {
583                         // not really ret_type is array, but ohwell
584                         pp->ret_type.is_array = 1;
585                         p = strchr(p + 1, ']');
586                         if (p == NULL) {
587                                 printf("%s:%d:%zd: ']' expected\n",
588                                  hdrfn, hdrfline, (p - protostr) + 1);
589                                 return -1;
590                         }
591                         p = sskip(p + 1);
592                 }
593                 if (*p != ')') {
594                         printf("%s:%d:%zd: ')' expected\n",
595                                 hdrfn, hdrfline, (p - protostr) + 1);
596                         return -1;
597                 }
598                 p = sskip(p + 1);
599         }
600
601         if (*p != '(') {
602                 printf("%s:%d:%zd: '(' expected, got '%c'\n",
603                                 hdrfn, hdrfline, (p - protostr) + 1, *p);
604                 return -1;
605         }
606         p++;
607
608         // check for x(void)
609         p = sskip(p);
610         if ((!strncmp(p, "void", 4) || !strncmp(p, "VOID", 4))
611            && *sskip(p + 4) == ')')
612                 p += 4;
613
614         while (1) {
615                 p = sskip(p);
616                 if (*p == ')') {
617                         p++;
618                         break;
619                 }
620                 if (xarg > 0) {
621                         if (*p != ',') {
622                                 printf("%s:%d:%zd: ',' expected\n",
623                                  hdrfn, hdrfline, (p - protostr) + 1);
624                                 return -1;
625                         }
626                         p = sskip(p + 1);
627                 }
628
629                 if (!strncmp(p, "...", 3)) {
630                         pp->is_vararg = 1;
631                         p = sskip(p + 3);
632                         if (*p == ')') {
633                                 p++;
634                                 break;
635                         }
636                         printf("%s:%d:%zd: ')' expected\n",
637                                 hdrfn, hdrfline, (p - protostr) + 1);
638                         return -1;
639                 }
640
641                 arg = &pp->arg[xarg];
642                 xarg++;
643
644                 p1 = p;
645                 ret = check_type(p, &arg->type);
646                 if (ret <= 0) {
647                         printf("%s:%d:%zd: unhandled type for arg%d\n",
648                                 hdrfn, hdrfline, (p - protostr) + 1, xarg);
649                         return -1;
650                 }
651                 p = sskip(p + ret);
652
653                 if (*p == '(' || arg->type.is_struct) {
654                         // func ptr or struct
655                         ret = parse_arg(&p1, arg, xarg);
656                         if (ret < 0) {
657                                 printf("%s:%d:%zd: funcarg parse failed\n",
658                                         hdrfn, hdrfline, p1 - protostr);
659                                 return -1;
660                         }
661                         p = p1;
662                 }
663
664                 p = next_idt(buf, sizeof(buf), p);
665                 p = sskip(p);
666 #if 0
667                 if (buf[0] == 0) {
668                         printf("%s:%d:%zd: idt missing for arg%d\n",
669                                 hdrfn, hdrfline, (p - protostr) + 1, xarg);
670                         return -1;
671                 }
672 #endif
673                 arg->reg = NULL;
674
675                 ret = get_regparm(regparm, sizeof(regparm), p, &is_retreg);
676                 if (ret > 0) {
677                         p += ret;
678                         p = sskip(p);
679
680                         arg->reg = strdup(map_reg(regparm));
681                         arg->type.is_retreg = is_retreg;
682                         pp->has_retreg |= is_retreg;
683                 }
684
685                 if (IS(arg->type.name, "float")
686                       || IS(arg->type.name, "double"))
687                 {
688                         arg->type.is_float = 1;
689                 }
690
691                 if (!arg->type.is_ptr && (strstr(arg->type.name, "int64")
692                       || IS(arg->type.name, "double")))
693                 {
694                         arg->type.is_64bit = 1;
695                         // hack..
696                         pp_copy_arg(&pp->arg[xarg], arg);
697                         arg = &pp->arg[xarg];
698                         xarg++;
699                         free(arg->type.name);
700                         arg->type.name = strdup("dummy");
701                 }
702
703                 ret = check_struct_arg(arg);
704                 if (ret > 0) {
705                         pp->has_structarg = 1;
706                         arg->type.is_struct = 1;
707                         free(arg->type.name);
708                         arg->type.name = strdup("int");
709                         for (l = 0; l < ret; l++) {
710                                 pp_copy_arg(&pp->arg[xarg], arg);
711                                 xarg++;
712                         }
713                 }
714         }
715
716         if (xarg > 0 && (IS(cconv, "__fastcall") || IS(cconv, "__thiscall"))) {
717                 if (pp->arg[0].reg != NULL) {
718                         printf("%s:%d: %s with arg1 spec %s?\n",
719                                 hdrfn, hdrfline, cconv, pp->arg[0].reg);
720                 }
721                 pp->arg[0].reg = strdup("ecx");
722         }
723
724         if (xarg > 1 && IS(cconv, "__fastcall")) {
725                 if (pp->arg[1].reg != NULL) {
726                         printf("%s:%d: %s with arg2 spec %s?\n",
727                                 hdrfn, hdrfline, cconv, pp->arg[1].reg);
728                 }
729                 pp->arg[1].reg = strdup("edx");
730         }
731
732         pp->argc = xarg;
733
734         for (i = 0; i < pp->argc; i++) {
735                 if (pp->arg[i].reg == NULL)
736                         pp->argc_stack++;
737                 else
738                         pp->argc_reg++;
739         }
740
741         if (pp->argc == 1 && pp->arg[0].reg != NULL
742             && IS(pp->arg[0].reg, "ecx"))
743         {
744                 pp->is_fastcall = 1;
745         }
746         else if (pp->argc_reg == 2
747           && pp->arg[0].reg != NULL && IS(pp->arg[0].reg, "ecx")
748           && pp->arg[1].reg != NULL && IS(pp->arg[1].reg, "edx"))
749         {
750                 pp->is_fastcall = 1;
751         }
752
753         if (pp->is_vararg && (pp->is_stdcall || pp->is_fastcall)) {
754                 printf("%s:%d: vararg %s?\n", hdrfn, hdrfline, cconv);
755                 return -1;
756         }
757
758         return p - protostr;
759 }
760
761 static int pp_name_cmp(const void *p1, const void *p2)
762 {
763         const struct parsed_proto *pp1 = p1, *pp2 = p2;
764         return strcmp(pp1->name, pp2->name);
765 }
766
767 static int ps_name_cmp(const void *p1, const void *p2)
768 {
769         const struct parsed_struct *ps1 = p1, *ps2 = p2;
770         return strcmp(ps1->name, ps2->name);
771 }
772
773 // parsed struct cache
774 static struct parsed_struct *ps_cache;
775 static int ps_cache_size;
776 static int ps_cache_alloc;
777
778 static int struct_handler(FILE *fhdr, char *proto, int *line)
779 {
780         struct parsed_struct *ps;
781         char lstr[256], *p;
782         int offset = 0;
783         int m = 0;
784         int ret;
785
786         if (ps_cache_size >= ps_cache_alloc) {
787                 ps_cache_alloc = ps_cache_alloc * 2 + 64;
788                 ps_cache = realloc(ps_cache, ps_cache_alloc
789                                 * sizeof(ps_cache[0]));
790                 my_assert_not(ps_cache, NULL);
791                 memset(ps_cache + ps_cache_size, 0,
792                         (ps_cache_alloc - ps_cache_size)
793                          * sizeof(ps_cache[0]));
794         }
795
796         ps = &ps_cache[ps_cache_size++];
797         ret = sscanf(proto, "struct %255s {", ps->name);
798         if (ret != 1) {
799                 printf("%s:%d: struct parse failed\n", hdrfn, *line);
800                 return -1;
801         }
802
803         while (fgets(lstr, sizeof(lstr), fhdr))
804         {
805                 (*line)++;
806
807                 p = sskip(lstr);
808                 if (p[0] == '/' && p[1] == '/')
809                         continue;
810                 if (p[0] == '}')
811                         break;
812
813                 if (m >= ARRAY_SIZE(ps->members)) {
814                         printf("%s:%d: too many struct members\n",
815                                 hdrfn, *line);
816                         return -1;
817                 }
818
819                 hdrfline = *line;
820                 ret = parse_protostr(p, &ps->members[m].pp);
821                 if (ret < 0) {
822                         printf("%s:%d: struct member #%d/%02x "
823                                 "doesn't parse\n", hdrfn, *line,
824                                 m, offset);
825                         return -1;
826                 }
827                 ps->members[m].offset = offset;
828                 offset += 4;
829                 m++;
830         }
831
832         ps->member_count = m;
833
834         return 0;
835 }
836
837 // parsed proto cache
838 static struct parsed_proto *pp_cache;
839 static int pp_cache_size;
840 static int pp_cache_alloc;
841
842 static int b_pp_c_handler(char *proto, const char *fname,
843         int is_include, int is_osinc, int is_cinc)
844 {
845         int ret;
846
847         if (pp_cache_size >= pp_cache_alloc) {
848                 pp_cache_alloc = pp_cache_alloc * 2 + 64;
849                 pp_cache = realloc(pp_cache, pp_cache_alloc
850                                 * sizeof(pp_cache[0]));
851                 my_assert_not(pp_cache, NULL);
852                 memset(pp_cache + pp_cache_size, 0,
853                         (pp_cache_alloc - pp_cache_size)
854                          * sizeof(pp_cache[0]));
855         }
856
857         ret = parse_protostr(proto, &pp_cache[pp_cache_size]);
858         if (ret < 0)
859                 return -1;
860
861         pp_cache[pp_cache_size].is_include = is_include;
862         pp_cache[pp_cache_size].is_osinc = is_osinc;
863         pp_cache[pp_cache_size].is_cinc = is_cinc;
864         pp_cache_size++;
865         return 0;
866 }
867
868 static void build_caches(FILE *fhdr)
869 {
870         long pos;
871         int ret;
872
873         pos = ftell(fhdr);
874         rewind(fhdr);
875
876         ret = do_protostrs(fhdr, hdrfn, 0);
877         if (ret < 0)
878                 exit(1);
879
880         qsort(pp_cache, pp_cache_size, sizeof(pp_cache[0]), pp_name_cmp);
881         qsort(ps_cache, ps_cache_size, sizeof(ps_cache[0]), ps_name_cmp);
882         fseek(fhdr, pos, SEEK_SET);
883 }
884
885 static const struct parsed_proto *proto_parse(FILE *fhdr, const char *sym,
886         int quiet)
887 {
888         const struct parsed_proto *pp_ret;
889         struct parsed_proto pp_search;
890         char *p;
891
892         if (pp_cache == NULL)
893                 build_caches(fhdr);
894
895         // ugh...
896         if (sym[0] == '_' && !IS_START(sym, "__W"))
897                 sym++;
898
899         strcpy(pp_search.name, sym);
900         p = strchr(pp_search.name, '@');
901         if (p != NULL)
902                 *p = 0;
903
904         pp_ret = bsearch(&pp_search, pp_cache, pp_cache_size,
905                         sizeof(pp_cache[0]), pp_name_cmp);
906         if (pp_ret == NULL && !quiet)
907                 printf("%s: sym '%s' is missing\n", hdrfn, sym);
908
909         return pp_ret;
910 }
911
912 static const struct parsed_proto *proto_lookup_struct(FILE *fhdr,
913         const char *type, int offset)
914 {
915         struct parsed_struct ps_search, *ps;
916         int m;
917
918         if (pp_cache == NULL)
919                 build_caches(fhdr);
920         if (ps_cache_size == 0)
921                 return NULL;
922
923         while (my_isblank(*type))
924                 type++;
925         if (!strncmp(type, "struct", 6) && my_isblank(type[6]))
926                 type += 7;
927
928         if (sscanf(type, "%255s", ps_search.name) != 1)
929                 return NULL;
930
931         ps = bsearch(&ps_search, ps_cache, ps_cache_size,
932                         sizeof(ps_cache[0]), ps_name_cmp);
933         if (ps == NULL) {
934                 printf("%s: struct '%s' is missing\n",
935                         hdrfn, ps_search.name);
936                 return NULL;
937         }
938
939         for (m = 0; m < ps->member_count; m++) {
940                 if (ps->members[m].offset == offset)
941                         return &ps->members[m].pp;
942         }
943
944         return NULL;
945 }
946
947 static void pp_copy_arg(struct parsed_proto_arg *d,
948         const struct parsed_proto_arg *s)
949 {
950         memcpy(d, s, sizeof(*d));
951
952         if (s->reg != NULL) {
953                 d->reg = strdup(s->reg);
954                 my_assert_not(d->reg, NULL);
955         }
956         if (s->type.name != NULL) {
957                 d->type.name = strdup(s->type.name);
958                 my_assert_not(d->type.name, NULL);
959         }
960         if (s->pp != NULL) {
961                 d->pp = malloc(sizeof(*d->pp));
962                 my_assert_not(d->pp, NULL);
963                 memcpy(d->pp, s->pp, sizeof(*d->pp));
964         }
965 }
966
967 struct parsed_proto *proto_clone(const struct parsed_proto *pp_c)
968 {
969         struct parsed_proto *pp;
970         int i;
971
972         pp = malloc(sizeof(*pp));
973         my_assert_not(pp, NULL);
974         memcpy(pp, pp_c, sizeof(*pp)); // lazy..
975
976         // do the actual deep copy..
977         for (i = 0; i < pp_c->argc; i++)
978                 pp_copy_arg(&pp->arg[i], &pp_c->arg[i]);
979         if (pp_c->ret_type.name != NULL)
980                 pp->ret_type.name = strdup(pp_c->ret_type.name);
981
982         return pp;
983 }
984
985
986 static inline int pp_cmp_func(const struct parsed_proto *pp1,
987   const struct parsed_proto *pp2)
988 {
989   int i;
990
991   if (pp1->argc != pp2->argc || pp1->argc_reg != pp2->argc_reg)
992     return 1;
993   else {
994     for (i = 0; i < pp1->argc; i++) {
995       if ((pp1->arg[i].reg != NULL) != (pp2->arg[i].reg != NULL))
996         return 1;
997
998       if ((pp1->arg[i].reg != NULL)
999         && !IS(pp1->arg[i].reg, pp2->arg[i].reg))
1000       {
1001         return 1;
1002       }
1003     }
1004   }
1005
1006   return 0;
1007 }
1008
1009 static inline void pp_print(char *buf, size_t buf_size,
1010   const struct parsed_proto *pp)
1011 {
1012   size_t l;
1013   int i;
1014
1015   snprintf(buf, buf_size, "%s %s(", pp->ret_type.name, pp->name);
1016   l = strlen(buf);
1017
1018   for (i = 0; i < pp->argc_reg; i++) {
1019     snprintf(buf + l, buf_size - l, "%s%s",
1020       i == 0 ? "" : ", ", pp->arg[i].reg);
1021     l = strlen(buf);
1022   }
1023   if (pp->argc_stack > 0) {
1024     snprintf(buf + l, buf_size - l, "%s{%d stack}",
1025       i == 0 ? "" : ", ", pp->argc_stack);
1026     l = strlen(buf);
1027   }
1028   snprintf(buf + l, buf_size - l, ")");
1029 }
1030
1031 static inline void proto_release(struct parsed_proto *pp)
1032 {
1033         int i;
1034
1035         for (i = 0; i < pp->argc; i++) {
1036                 if (pp->arg[i].reg != NULL)
1037                         free(pp->arg[i].reg);
1038                 if (pp->arg[i].type.name != NULL)
1039                         free(pp->arg[i].type.name);
1040                 if (pp->arg[i].pp != NULL)
1041                         free(pp->arg[i].pp);
1042         }
1043         if (pp->ret_type.name != NULL)
1044                 free(pp->ret_type.name);
1045         free(pp);
1046
1047         (void)proto_lookup_struct;
1048 }