/*
   Copyright (c) 2009 mingw-w64 project

   Contributing authors: Kai Tietz, Jonathan Yong

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "gendef.h"

#define ENABLE_DEBUG 0

#if ENABLE_DEBUG == 1
#define PRDEBUG(ARG...)  fprintf(stderr,ARG)
#else
#define PRDEBUG(ARG...) do { } while(0)
#endif

typedef enum eSPNA {
  SPNA_OPER_MINUSEQ=0,SPNA_OPER_PLUSEQ, SPNA_OPER_MULEQ,SPNA_OPER_OROR,SPNA_OPER_ANDAND,SPNA_OPER_OR,SPNA_OPER_XOR,
  SPNA_OPER_INV, SPNA_OPER_COMMA,SPNA_OPER_CAST, SPNA_OPER_SHR,SPNA_OPER_SHL,SPNA_OPER_SHREQ,SPNA_OPER_SHLEQ,
  SPNA_OPER_NOT,SPNA_OPER_EQEQ,SPNA_OPER_NOTEQ,SPNA_OPER_ARRAY,
  SPNA_OPER_RETURNTYPE,SPNA_OPER_POINTTO,SPNA_OPER_MUL,
  SPNA_OPER_PLUSPLUS,SPNA_OPER_MINUSMINUS,SPNA_OPER_MINUS,SPNA_OPER_PLUS,
  SPNA_OPER_AND,SPNA_OPER_POINTTOSTAR,SPNA_OPER_DIV,SPNA_OPER_MOD,
  SPNA_OPER_NEW_ARRAY,SPNA_OPER_DELETE_ARRAY,
  SPNA_CONSTRUCTOR,SPNA_DESTRUCTOR,SPNA_OPER_NEW,SPNA_OPER_DELETE,
  SPNA_OPER_EQ,
  SPNA_OPER_GREATEQ,SPNA_OPER_GREAT,SPNA_OPER_LESSEQ,SPNA_OPER_LESS,
  SPNA_OPER_DIVEQ,SPNA_OPER_MODEQ,SPNA_OPER_ANDEQ,SPNA_OPER_OREQ,SPNA_OPER_XOREQ,

  SPNA_RTTI_CLASS_HIERACHY_DESCRIPTOR,SPNA_RTTI_COMPLETE_OBJECT_LOCATOR,
  SPNA_LOCAL_VFTABLE,SPNA_LOCAL_VFTABLE_CONSTRUCTOR_CLOSURE,
  SPNA_PLACEMENT_DELETE_CLOSURE,SPNA_PLACEMENT_DELETE_ARRAY_CLOSURE,
  SPNA_VECTOR_DESTRUCTOR_ITER,SPNA_VECTOR_VBASE_CONSTRUCTOR_ITER,
  SPNA_VIRTUAL_DISPLACEMENT_MAP,SPNA_EH_VECTOR_CONSTRUCTOR_ITER,
  SPNA_EH_VECTOR_DESTRUCTOR_ITER,SPNA_EH_VECTOR_VBASE_CONSTRUCTOR_ITER,
  SPNA_COPY_CONSTRUCTOR_CLOSURE,SPNA_UDT_RETURNING,
  SPNA_RTTI_TYPE_DESCRIPTOR,SPNA_RTTI_BASE_CLASS_DESCRIPTOR,
  SPNA_RTTI_CLASS_ARRAY,
  SPNA_MANAGED_VECTOR_CONSTRUCTOR_ITER,SPNA_MANAGED_VECTOR_DESTRUCTOR_ITER,
  SPNA_EH_VECTOR_COPY_CONSTRUCTOR_ITER,SPNA_EH_VECTOR_VBASE_COPY_CONSTRUCTOR_ITER,
  SPNA_VFTABLE,SPNA_VBTABLE,SPNA_VCALL,SPNA_TYPEOF,SPNA_LOCAL_STATIC_GUARD,
  SPNA_STRING,SPNA_VBASE_DESTRUCTOR,SPNA_VECTOR_DELETING_DESTRUCTOR,
  SPNA_VECTOR_DEFAULT_CONSTRUCTOR_CLOSURE,SPNA_SCALAR_DELETEING_DESTRUCTOR,
  SPNA_VECTOR_CONSTRUCTOR_ITER,
  SPNA_UNKNOWN,SPNA_MAX
} eSPNA;

static const char *spna_names[SPNA_MAX] = {
  "-=","+=", "*=","||","&&","|","^",
  "~",",","operand ()", ">>","<<",">>=","<<=",
  "!","==","!=","operand []",
  "return type","->","operand *",
  "++","--","-","+",
  "&","->*","/","%",
  "new []","delete []", "()","~()","new","delete",
  "=",
  ">=",">","<=","<",
  "/=","%=","&=","|=","^=",

  "RTTI_CLASS_HIERACHY_DESCRIPTOR","RTTI_COMPLETE_OBJECT_LOCATOR",
  "LOCAL_VFTABLE","LOCAL_VFTABLE_CONSTRUCTOR_CLOSURE",
  "PLACEMENT_DELETE_CLOSURE","PLACEMENT_DELETE_ARRAY_CLOSURE",
  "VECTOR_DESTRUCTOR_ITER","VECTOR_VBASE_CONSTRUCTOR_ITER",
  "VIRTUAL_DISPLACEMENT_MAP","EH_VECTOR_CONSTRUCTOR_ITER",
  "EH_VECTOR_DESTRUCTOR_ITER","EH_VECTOR_VBASE_CONSTRUCTOR_ITER",
  "COPY_CONSTRUCTOR_CLOSURE","UDT_RETURNING",
  "RTTI_TYPE_DESCRIPTOR","RTTI_BASE_CLASS_DESCRIPTOR",
  "RTTI_CLASS_ARRAY",

  "MANAGED_VECTOR_CONSTRUCTOR_ITER","MANAGED_VECTOR_DESTRUCTOR_ITER",
  "EH_VECTOR_COPY_CONSTRUCTOR_ITER","EH_VECTOR_VBASE_COPY_CONSTRUCTOR_ITER",
  "VFTABLE","VBTABLE","VCALL","TYPEOF","LOCAL_STATIC_GUARD",
  "STRING","VBASE_DESTRUCTOR","VECTOR_DELETING_DESTRUCTOR",
  "VECTOR_DEFAULT_CONSTRUCTOR_CLOSURE","SCALAR_DELETEING_DESTRUCTOR",
  "VECTOR_CONSTRUCTOR_ITER",

  "unknown"
};

void decode_mangle(const char *n);

static int load_pep(void);
static void do_pedef(void);
static void do_pepdef(void);
static void do_export_read(uint32_t va_exp,uint32_t sz_exp,int be64);
static void add_export_list(uint32_t ord,uint32_t func,const char *name, const char *forward,int be64,int beData);
static void dump_def(void);
static int disassembleRet(uint32_t func,uint32_t *retpop,const char *name);
static size_t getMemonic(int *aCode,uint32_t pc,volatile uint32_t *jmp_pc,const char *name);

static void *map_va (uint32_t va);
static int is_data (uint32_t va);
static int is_reloc (uint32_t va);

static int disassembleRetIntern(uint32_t pc,uint32_t *retpop,sAddresses *seen,sAddresses *stack,int *hasret,int *atleast_one,const char *name);
static sAddresses*init_addr(void);
static void dest_addr(sAddresses *ad);
static int push_addr(sAddresses *ad,uint32_t val);
static int pop_addr(sAddresses *ad,uint32_t *val);

static sExportName *gExp = NULL;
static sExportName *gExpTail = NULL;
char *fninput;
char *fnoutput;
char *fndllname;

size_t gDta_size;
unsigned char *gDta;
PIMAGE_DOS_HEADER gMZDta;
PIMAGE_NT_HEADERS32 gPEDta;
PIMAGE_NT_HEADERS64 gPEPDta;

static int std_output = 0;

static Gendefopts *chain_ptr = NULL;
 __attribute__((noreturn)) static void show_usage (void);
void opt_chain (const char *);

void opt_chain (const char *opts)
{
  static Gendefopts *prev, *current;
  char *r1, *r2;
  
  if (!strncmp (opts, "-", 2))
    {
      std_output = 1;
      return;
    }
  if (!strncmp (opts, "--help", 7))
    {
      show_usage();
      return;
    }

  current = malloc (sizeof(Gendefopts));
  if (current)
    {
      memset (current, 0, sizeof (Gendefopts));
      current->next = NULL;

      if (!prev)
        chain_ptr = current;
      else
        prev->next = current;
      r1 = strrchr (opts,'/');
      r2 = strrchr (opts,'\\');
      current->fninput = strdup (opts);
      
      if (!r1 && r2 != NULL)
        r1 = r2 + 1;
      else if(r1 == NULL && r2 == NULL)
        r1 = current->fninput;
      else if (r2 != NULL && r1 != NULL && r1 < r2)
        r1 = r2 + 1;
      else
        r1++;
      current->fnoutput = (char*) malloc (strlen (current->fninput) + 5);
      strcpy (current->fnoutput,r1);

      r1 = strrchr (current->fnoutput,'.');
      if (r1)
        strcpy (r1,".def");
      else
        strcat (current->fnoutput,".def");
      prev = current;
   }
  return;
}

void
show_usage (void)
{
  fprintf (stderr, "Usage: gendef [OPTION]... [DLL]...\n");
  fprintf (stderr, "Dumps DLL exports information from PE32/PE32+ executables\n");
  fprintf (stderr, "\n");
  fprintf (stderr, "Options:\n"
    "  -                        Dump to stdout\n"
    "      --help               Show this help.\n"
  );
  fprintf (stderr, "\n");
  fprintf (stderr, "Usage example: \n"
                   "  By default, the output files are named after their DLL counterparts\n"
                   "  gendef MYDLL.DLL     Produces MYDLL.def\n"
                   "  gendef - MYDLL.DLL   Prints the exports to stdout\n");
  fprintf (stderr, "\nReport bugs to <mingw-w64-public@lists.sourceforge.net>\n");
  exit (0);
}

int main(int argc,char **argv)
{
  int i;
  Gendefopts *opt;

  if (argc < 2)
  {
    show_usage();
    return 0;
  }

  for (i = 1; i < argc; i++)
    opt_chain (argv[i]);
  opt = chain_ptr;
  while (opt)
    {
      fninput = opt->fninput;
      fnoutput = opt->fnoutput;

      if (load_pep ())
        {
	  if (gPEDta || gPEPDta)
	    {
	      if (gPEDta)
		do_pedef ();
	      else
		do_pepdef ();
	      dump_def ();
	    }
	}
      if (fndllname)
	free (fndllname);
      fndllname = NULL;
      if (gDta)
	{
	  free (gDta);
	  gDta = NULL;
	}
      free(opt->fninput);
      free(opt->fnoutput);
      free(opt);
      opt = opt->next;
    }
  return 0;
}

static int
load_pep(void)
{
  FILE *fp = fopen (fninput, "rb");

  if (!fp)
    {
      fprintf (stderr, "*** [%s] failed to open()\n", fninput);
      return 0;
    }
  fseek (fp, 0, SEEK_END);
  gDta_size = (size_t) ftell (fp);
  if (gDta_size > 0) {
    fseek (fp,0,SEEK_SET);
    gDta = (unsigned char*) malloc (gDta_size + 1);
    if (gDta)
      {
        if (fread (gDta, gDta_size, 1, fp) != 1)
	  {
	    free (gDta);
	    gDta = NULL;
	  }
	else
          gDta[gDta_size] = 0;
    }
  }
  fclose (fp);
  if (!gDta)
    {
      fprintf (stderr, "*** [%s] unable to allocate %lu bytes\n", fninput,
	       (unsigned long) gDta_size);
      return 0;
    }
  gMZDta = (PIMAGE_DOS_HEADER) gDta;
  if (gDta_size < sizeof(IMAGE_DOS_HEADER) || gDta[0]!='M' || gDta[1]!='Z'
      || gMZDta->e_lfanew <= 0
      || gMZDta->e_lfanew >= (int32_t) gDta_size)
  {
    fprintf(stderr,"*** [%s] not a PE(+) file\n", fninput);
    free(gDta);
    gDta = NULL;
    return 0;
  }
  gPEDta = (PIMAGE_NT_HEADERS32) &gDta[gMZDta->e_lfanew];
  gPEPDta = (PIMAGE_NT_HEADERS64) gPEDta;
  if (gPEDta->Signature != IMAGE_NT_SIGNATURE)
    {
      fprintf (stderr, "*** [%s] no PE(+) signature\n", fninput);
      free (gDta);
      gDta = NULL;
      gPEPDta = NULL;
      gPEDta = NULL;
      return 0;
    }
  if (gPEDta->FileHeader.SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL32_HEADER
      && gPEDta->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
    {
      gPEPDta = NULL;
      fprintf (stderr, " * [%s] Found PE image\n", fninput);
    }
  else if (gPEPDta->FileHeader.SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL64_HEADER
    && gPEPDta->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    {
      gPEDta = NULL;
      fprintf (stderr, " * [%s] Found PE+ image\n", fninput);
    }
  else
    {
      free (gDta);
      gDta = NULL;
      fprintf (stderr, "*** [%s] no PE(+) optional header\n", fninput);
      gPEPDta = NULL;
      gPEDta = NULL;
      return 0;
    }
  return 1;
}

static int
is_data (uint32_t va)
{
  PIMAGE_SECTION_HEADER sec;
  uint32_t sec_cnt,i;

  /* If export va is directly relocated, so it must be data.  */  
  if (is_reloc (va))
    return 1;
  
  if (gPEDta)
    {
      sec_cnt = gPEDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEDta);
    }
  else
    {
      sec_cnt = gPEPDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEPDta);
    }
  if (!sec)
    return 0;
  for (i = 0;i < sec_cnt;i++)
    {
      if (va >= sec[i].VirtualAddress && va < (sec[i].VirtualAddress+sec[i].Misc.VirtualSize))
        break;
    }
  if (i == sec_cnt)
    return 0; 
  if ((sec[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE)) != 0)
    return 0;
  if ((sec[i].Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0)
    return 1;
  if ((sec[i].Characteristics & IMAGE_SCN_MEM_READ) ==0)
    return 0;
  if ((sec[i].Characteristics & (IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_LNK_COMDAT)) != 0)
    return 1;
  return 1;
}

static int
is_reloc (uint32_t va)
{
  uint32_t va_rel, sz_rel, pos;
  unsigned char *p;
  PIMAGE_BASE_RELOCATION brel;

  if (gPEDta)
    {
      va_rel = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      sz_rel = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    }
  else
    {
      va_rel = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      sz_rel = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    }
  if (va_rel == 0 || sz_rel < IMAGE_SIZEOF_BASE_RELOCATION)
    return 0;
  p = (unsigned char *) map_va (va_rel);
  for (pos = 0; pos < sz_rel;)
    {
      uint16_t *r;
      uint32_t nums, j;
      if ((sz_rel - pos) < IMAGE_SIZEOF_BASE_RELOCATION)
        break;
      brel = (PIMAGE_BASE_RELOCATION) &p[pos];
      if (brel->SizeOfBlock == 0)
         break;
      pos += IMAGE_SIZEOF_BASE_RELOCATION;
      nums = (brel->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2;
      r = (uint16_t *) &p[pos];
      if (va >= brel->VirtualAddress && va < (brel->VirtualAddress + 0x1008))
        {
          for (j = 0; j < nums; j++)
            {
              uint32_t relsz = 0;
              uint32_t offs = (uint32_t) (r[j] & 0xfff) + brel->VirtualAddress;
              uint16_t typ = (r[j] >> 12) & 0xf;
              if (typ == IMAGE_REL_BASED_HIGHADJ)
                j++;
              switch (typ)
                {
                case IMAGE_REL_BASED_HIGHLOW:
                  relsz = 4;
                  break;
                case IMAGE_REL_BASED_DIR64:
                  relsz = 8;
                  break;
                }
              if (relsz != 0 && va >= offs && va < (offs + relsz))
                return 1;
            }
        }
      pos += (brel->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION);
    }
  return 0;
}

static void *
map_va (uint32_t va)
{
  PIMAGE_SECTION_HEADER sec;
  uint32_t sec_cnt,i;
  char *dptr;

  if (gPEDta)
    {
      sec_cnt = gPEDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEDta);
    }
  else
    {
      sec_cnt = gPEPDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEPDta);
    }
  for (i = 0;i < sec_cnt;i++)
    {
      if (va >= sec[i].VirtualAddress && va < (sec[i].VirtualAddress+sec[i].Misc.VirtualSize))
        {
          dptr = (char*) &gDta[va-sec[i].VirtualAddress+sec[i].PointerToRawData];
          return (void *)dptr;
        }
    }
  return NULL;
}

/* For pep we can take the exports itself, there is no additional decoration necessary.  */
static void
do_pepdef (void)
{
  uint32_t va_exp = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  uint32_t sz_exp = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
  do_export_read (va_exp, sz_exp,1);
}

static void
do_pedef (void)
{
  uint32_t va_exp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  uint32_t sz_exp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
  do_export_read (va_exp, sz_exp, 0);
}

static void
do_export_read (uint32_t va_exp, uint32_t sz_exp, int be64)
{
  uint32_t i;
  PIMAGE_EXPORT_DIRECTORY exp_dir;
  uint32_t *functions;
  uint16_t *ordinals;
  uint32_t *name;

  if (va_exp == 0 || sz_exp == 0)
    return;
  exp_dir = (PIMAGE_EXPORT_DIRECTORY) map_va(va_exp);
  PRDEBUG(" * export directory at VA = 0x%x size=0x%x\n", (unsigned int) va_exp, (unsigned int) sz_exp);
  fndllname = strdup ((char*) map_va (exp_dir->Name));
  PRDEBUG(" * Name: %s\n * Base: %u\n", fndllname, (unsigned int) exp_dir->Base);
  functions = (uint32_t *) map_va (exp_dir->AddressOfFunctions);
  ordinals = (uint16_t *) map_va (exp_dir->AddressOfNameOrdinals);
  name = (uint32_t *) map_va (exp_dir->AddressOfNames);
  
  for (i = 0;i < exp_dir->NumberOfFunctions;i++)
    {
      uint32_t entryPointRVA = functions[i];
      uint32_t j;
      char *fname;
      uint32_t ord;
      fname = NULL;
      if (!entryPointRVA)
        continue;
      ord = i + exp_dir->Base;
      for (j = 0;j < exp_dir->NumberOfNames;j++)
        if (ordinals[j]==i)
          fname = (char*) map_va (name[j]);
      if (entryPointRVA >= va_exp && entryPointRVA <= (va_exp + sz_exp))
        add_export_list (ord, 0, fname,(char*) map_va (entryPointRVA), be64, 0);
      else
        add_export_list(ord, entryPointRVA, fname, NULL, be64, is_data (entryPointRVA));
    }
}

static void
add_export_list(uint32_t ord,uint32_t func,const char *name, const char *forward,int be64,int beData)
{
  sExportName *exp = NULL;

  if (!name)
    name = "";
  if (!forward)
    forward = "";
  exp = (sExportName *) malloc (sizeof (sExportName) + strlen (name) + strlen (forward) + 2);
  if (!exp)
    return;
  exp->name = (char *) &exp[1];
  exp->forward = exp->name + strlen (name) + 1;
  strcpy (exp->name, name);
  strcpy (exp->forward, forward);
  exp->next = NULL;
  exp->ord = ord;
  exp->func = func;
  exp->be64 = be64;
  exp->beData = beData;
  exp->retpop = (uint32_t)-1;
  if (gExpTail)
    gExpTail->next = exp;
  else
    gExp = exp;
  gExpTail = exp;
}

static void
dump_def(void)
{
  sExportName *exp;
  FILE *fp;

  if (!fndllname || gExp == NULL)
    return;
  if (!std_output)
    fp = fopen(fnoutput,"wt");
  else
    fp = stdout;
  if(!fp) {
    fprintf(stderr," * failed to create %s ...\n",fnoutput);
    return;
  }
  fprintf (fp,";\n; Definition file of %s\n; Automatic generated by gendef\n; written by Kai Tietz 2008\n;\n",
    fndllname);
  fprintf (fp,"LIBRARY \"%s\"\nEXPORTS\n",fndllname);
  while ((exp = gExp) != NULL)
    {
      gExp = exp->next;
      if (exp->name[0]==0)
        fprintf(fp,"ord_%u", (unsigned int) exp->ord);
      else
        fprintf(fp,"%s",exp->name);
      if (exp->name[0]=='?')
        {
          /* decode_mangle(exp->name); */
        }
      if (exp->name[0]=='?' && exp->name[1]=='?')
        {
          if (!strncmp(exp->name,"??_7",4)) exp->beData=1;
        }
      if (!exp->beData && !exp->be64 && exp->func != 0)
        exp->beData = disassembleRet (exp->func, &exp->retpop,exp->name);
      if (exp->retpop != (uint32_t) -1)
        {
          if (exp->name[0]=='?')
            fprintf(fp," ; has WINAPI (@%u)", (unsigned int) exp->retpop);
          else
            fprintf(fp,"@%u", (unsigned int) exp->retpop);
        }
      if (exp->name[0]==0)
        fprintf(fp," @%u", (unsigned int) exp->ord);
      if (exp->beData)
        fprintf(fp," DATA");
      fprintf(fp,"\n");
      free (exp);
    }
  gExpTail=NULL;
  if (!std_output)
    fclose(fp);
}

static sAddresses *
init_addr (void)
{
  sAddresses *r = (sAddresses*) malloc (sizeof (sAddresses));
  r->max = 8;
  r->cnt = 0;
  r->ptrs = (uint32_t *) malloc (sizeof (uint32_t) * 8);
  r->idx = 0;
  return r;
}

static void
dest_addr (sAddresses *ad)
{
  free (ad->ptrs);
  free (ad);
}

static int
push_addr (sAddresses *ad, uint32_t val)
{
  uint32_t i;

  for (i = 0;i < ad->cnt; i++)
    {
      if (ad->ptrs[i] == val)
        return 0;
    }
  if (ad->max == ad->cnt)
    {
      uint32_t *p = (uint32_t *) malloc (sizeof (uint32_t) * (ad->max + 8));

      memcpy (p, ad->ptrs, sizeof (uint32_t) * ad->max);
      ad->max += 8;
      free (ad->ptrs);
      ad->ptrs = p;
    }
  ad->ptrs[ad->cnt++] = val;
  return 1;
}

static int
pop_addr (sAddresses *ad, uint32_t *val)
{
  if (!ad || ad->idx == ad->cnt)
    return 0;
  ad->idx++;
  *val = ad->ptrs[ad->idx-1];
  return 1;
}

/* exp->beData */
static int
disassembleRet (uint32_t func, uint32_t *retpop, const char *name)
{
  sAddresses *seen = init_addr ();
  sAddresses *stack = init_addr ();
  uint32_t pc;
  int hasret = 0;
  int atleast_one = 0;
  *retpop = (uint32_t) -1;
  push_addr (stack, func);

  while (!hasret && pop_addr(stack,&pc))
    {
      if (disassembleRetIntern (pc, retpop, seen, stack, &hasret, &atleast_one,name))
        break;
    }
  dest_addr (seen);
  dest_addr (stack);
  return (atleast_one ? 0 : 1);
}

static int
disassembleRetIntern(uint32_t pc, uint32_t *retpop, sAddresses *seen, sAddresses *stack,
		     int *hasret, int *atleast_one, const char *name)
{
  size_t sz = 0;
  int code = 0,break_it = 0;
  volatile uint32_t tojmp = 0;

  while(1)
    {
      if (!push_addr (seen, pc))
        return 0;
      sz = getMemonic (&code, pc, &tojmp, name) & 0xffffffff;
      if (!sz || code == c_ill)
        {
          PRDEBUG(" %s = 0x08%x ILL (%u) at least one==%d\n",name,
	 	  (unsigned int) pc, (unsigned int) sz,atleast_one[0]);
#if ENABLE_DEBUG == 1
      {
        unsigned char *ppc = (unsigned char*) map_va (pc);
        size_t i;

        fprintf (stderr, "%s(0x%x): ",name, (unsigned int) pc);
        for (i = 0;i < sz; i++)
          {
            fprintf (stderr, "%s0x%x", (i == 0 ? " ":","), ppc[i]);
          }
        fprintf (stderr, "\n");
      }
      exit (0);
#endif
          break;
        }
#if ENABLE_DEBUG == 1
      {
        unsigned char *ppc = (unsigned char*) map_va (pc);
        size_t i;

        fprintf (stderr, "%s(0x%x): ",name, (unsigned int) pc);
        for (i = 0;i < sz; i++)
          {
            fprintf (stderr, "%s0x%x", (i == 0 ? " ":","), ppc[i]);
          }
        fprintf (stderr, "\n");
      }
#endif
      atleast_one[0] += 1;
      break_it = 0;
      pc += sz;
      switch(code)
        {
        case c_jmpnjb: case c_jmpnjv:
          pc = tojmp;
          break;
        case c_jxx:
          push_addr (stack, tojmp);
          break;
        case c_jmpfap: case c_int3:
          break_it = 1;
          break;
        case c_iret: case c_retf: case c_retn:
          *hasret = 1;
          return 1;
        case c_retflw: case c_retnlw:
          *hasret = 1;
          *retpop = tojmp;
          return 1;
        }
      if (break_it)
        break;
    }

  return 0;
}

static int opMap2[256] = {
  c_EG,c_EG,c_EG,c_EG,c_1,c_1,c_ill,c_ill, /* 0x00-0x07 */
  c_1,c_1,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill, /* 0x08-0x0f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x10-0x17 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x18-0x1f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_ill,c_EG,c_ill, /* 0x20-0x27 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x28-0x2f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_ill,c_1, /* 0x30-0x37 */
  c_ill,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill, /* 0x38-0x3f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x40-0x47 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x48-0x4f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x50-0x57 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x58-0x5f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x60-0x67 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x68-0x6f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x70-0x77 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x78-0x7f */
  c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv, /* 0x80-0x87 */
  c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv, /* 0x88-0x8f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x90-0x97 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x98-0x9f */
  c_1,c_1,c_1,c_EG,c_EGlb,c_EG,c_ill,c_ill, /* 0xa0-0xa7 */
  c_1,c_1,c_1,c_EG,c_EGlb,c_EG,c_EG,c_EG, /* 0xa8-0xaf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xb0-0xb7 */
  c_EG,c_1,c_EGlb,c_EG, c_EG,c_EG,c_EG,c_EG, /* 0xb8-0xbf */
  c_EG,c_EG,c_EGlb,c_EG,c_EGlb,c_EGlb,c_EGlb,c_EG, /* 0xc0-0xc7 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0xc8-0xcf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd0-0xd7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd8-0xdf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xe0-0xe7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xe8-0xef */
  c_ill,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xf0-0xf7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_ill /* 0xf8-0xff */
};

static int opMap1[256] = {
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x00-0x07 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_0f, /* 0x08-0x0f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x10-0x17 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x18-0x1f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x20-0x27 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x28-0x2f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x30-0x37 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x38-0x3f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x40-0x47 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x48-0x4f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x50-0x57 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x58-0x5f */
  c_1,c_1,c_EG,c_EG,c_1,c_1,c_op,c_ad, /* 0x60-0x67 */
  c_lv,c_EGlv,c_lb,c_EGlb,c_1,c_1,c_1,c_1, /* 0x68-0x6f */
  c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx, /* 0x70-0x77 */
  c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx, /* 0x78-0x7f */
  c_EGlb,c_EGlv,c_EGlb,c_EGlb,c_EG,c_EG,c_EG,c_EG, /* 0x80-0x87 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x88-0x8f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x90-0x97 */
  c_1,c_1,c_callfar,c_1,c_1,c_1,c_1,c_1, /* 0x98-0x9f */
  c_O,c_O,c_O,c_O,c_1,c_1,c_1,c_1, /* 0xa0-0xa7 */
  c_lb,c_lv,c_1,c_1,c_1,c_1,c_1,c_1, /* 0xa8- 0xaf */
  c_2,c_2,c_2,c_2,c_2,c_2,c_2,c_2, /* 0xb0-0xb7 */
  c_lv,c_lv,c_lv,c_lv,c_lv,c_lv,c_lv,c_lv, /* 0xb8-0xbf */
  c_EGlb,c_EGlb,c_retnlw,c_retn,c_EG,c_EG,c_EGlb,c_EGlv, /* 0xc0-0xc7 */
  c_4,c_1,c_retflw,c_retf,c_int3,c_2,c_1,c_iret, /* 0xc8-0xcf */
  c_EG,c_EG,c_EG,c_EG,c_2,c_2,c_1,c_1, /* 0xd0-0xd7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd8-0xdf */
  c_jxx,c_jxx,c_jxx,c_jxx,c_2,c_2,c_2,c_2, /* 0xe0-0xe7 */
  c_calljv,c_jmpnjv,c_jmpfap,c_jmpnjb,c_1,c_1,c_1,c_1, /* 0xe8-0xef */
  c_1,c_1,c_1,c_1, c_1,c_1,c_EGg3b,c_EGg3v, /* 0xf0-0xf7 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_g4,c_g4 /* 0xf8-0xff */
};

#if ENABLE_DEBUG == 1

#define MAX_INSN_SAVE	20

static void
enter_save_insn(unsigned char *s, unsigned char b)
{
  int i;
  for (i=0;i<MAX_INSN_SAVE-1;i++)
    s[i]=s[i+1];
  s[MAX_INSN_SAVE-1]=b;
}

static void
print_save_insn(const char *name, unsigned char *s)
{
  int i;
  
  PRDEBUG("From %s: ",name);
  for (i=0;i<MAX_INSN_SAVE;i++)
  {
    PRDEBUG("%s0x%x",(i!=0 ? "," : ""), (unsigned int) s[i]);
  }
}
#endif

static size_t
getMemonic(int *aCode,uint32_t pc,volatile uint32_t *jmp_pc, __attribute__((unused)) const char *name)
{
#if ENABLE_DEBUG == 1
  static unsigned char lw[MAX_INSN_SAVE];
#endif
  unsigned char *p;
  int addr_mode = 1;
  int oper_mode = 1;
  size_t sz = 0;
  unsigned char b;
  int tb1;

  for(;;) {
    p = (unsigned char*)map_va(pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    if (b==0x26 || b==0x2e || b==0x36 || b==0x3e || b==0x64 || b==0x65)
      ++sz;
    else if (b==0x66) { oper_mode^=1; sz++; }
    else if (b==0x67) { addr_mode^=1; sz++; }
    else if (b==0xf2 || b==0xf3 || b==0xf0) sz++;
    else break;
  }
  sz++;
  tb1=opMap1[(unsigned int) b];
  
redo_switch:
#if ENABLE_DEBUG == 1
  if (tb1!=c_ill) { enter_save_insn(lw,b); }
#endif
  switch (tb1) {
  case c_ill:
#if ENABLE_DEBUG == 1
    print_save_insn (name, lw);
    PRDEBUG(" 0x%x illegal ", (unsigned int) b);
#endif
    *aCode=c_ill; return 0;
  case c_4: sz++;
  case c_3: sz++;
  case c_lb:
  case c_2: sz++;
  case c_retn: case c_retf:
  case c_iret: case c_int3:
  case c_ad: case c_op:
  case c_1: *aCode=tb1; return sz;
  case c_lv:
    if (oper_mode) sz+=4;
    else sz+=2;
    *aCode=tb1; return sz;
  case c_O: case c_calljv:
    if (addr_mode) sz+=4;
    else sz+=2;
    *aCode=tb1; return sz;
  case c_EG: case c_EGlv: case c_EGlb: case c_g4: case c_EGg3v: case c_EGg3b:
    p = (unsigned char*)map_va(pc + sz);
    sz++;
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
#if ENABLE_DEBUG == 1
    enter_save_insn(lw,b);
#endif
    if (addr_mode) {
      if((b&0xc0)!=0xc0 && (b&0x7)==4)
        {
          p = (unsigned char*)map_va(pc + sz);
          if (!p) { *aCode=c_ill; return 0; }
#if ENABLE_DEBUG == 1
    enter_save_insn(lw,p[0]);
#endif
          b&=~0x7; b|=(p[0]&7);
	  sz+=1;
	}
      if((b&0xc0)==0 && (b&7)==5) { sz+=4; }
      else if((b&0xc0)==0x40)
	sz+=1;
      else if((b&0xc0)==0x80)
	sz+=4;
    } else {
      if((b&0xc0)==0) {
	if((b&0x07)==6) sz+=2;
      } else if((b&0xc0)==0x40)
	sz+=1;
      else if((b&0xc0)==0x80)
	sz+=2;
    }
    if (tb1==c_EGlv) sz+=(oper_mode ? 4 : 2);
    else if(tb1==c_EGlb) sz++;
    else if(tb1==c_g4) {
      if ((b&0x38)==0x20 || (b&0x38)==0x28)
	tb1=c_int3;
      else if((b&0x38)==0x38)
        tb1=c_ill;
    } else if (tb1==c_EGg3v || tb1==c_EGg3b) {
      switch (((b&0x38)>>3)) {
      case 1:
      case 0: sz+= (tb1==c_EGg3v ? (oper_mode ? 4 : 2) : 1); break;
      default: break;
      }
    }
    *aCode=tb1; return sz;
  case c_jxx: case c_jmpnjb:
    p = (unsigned char*)map_va(pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    sz++;
    jmp_pc[0]=(uint32_t) pc + (uint32_t) sz;
    if ((b&0x80)!=0)
      jmp_pc[0] = jmp_pc[0] + (((uint32_t) b) | 0xffffff00);
    else
      jmp_pc[0] = jmp_pc[0] + (uint32_t) b;
    *aCode=tb1; return sz;
  case c_jmpnjv:
  case c_jxxv: 
    p = (unsigned char*)map_va(pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    if (oper_mode) { jmp_pc[0]=*((uint32_t *)p); sz+=4; }
    else {
      jmp_pc[0]=(uint32_t) *((uint16_t *)p);
      if ((jmp_pc[0]&0x8000)!=0) jmp_pc[0]|=0xffff0000;
      sz+=2;
    }
    jmp_pc[0]+=(uint32_t) pc+(uint32_t) sz;
#if ENABLE_DEBUG == 1
    if ((jmp_pc[0]&0xff000000)!=0) {
      print_save_insn (name, lw);
      PRDEBUG(" 0x%x illegal ", (unsigned int) b);
      PRDEBUG("jmp(cond) 0x%x (sz=%x,pc=%x,off=%x) ",
	      jmp_pc[0], (unsigned int) sz,(unsigned int) pc,
              (unsigned int) (jmp_pc[0]-(sz+pc)));
    }
#endif
    *aCode=(tb1==c_jxxv ? c_jxx : tb1);
    return sz;
  case c_0f:
    p = (unsigned char*)map_va(pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    sz++;
    tb1=opMap2[b];
    goto redo_switch;
  case c_jmpfap:
    sz+=4; if(oper_mode) sz+=2;
    *aCode=tb1; return sz;
  case c_callfar:
    sz+=4; if(oper_mode) sz+=2;
    *aCode=tb1; return sz;
  case c_retflw: case c_retnlw:
    p = (unsigned char*)map_va(pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    jmp_pc[0]=*((uint16_t*) p);
    sz+=2;
    *aCode=tb1;
#if ENABLE_DEBUG == 1
    if (jmp_pc[0]>0x100 || jmp_pc[0]&3) {
      print_save_insn (name, lw);
      PRDEBUG(" 0x%x illegal ", (unsigned int) b);
      PRDEBUG("ret dw 0x%x (sz=%x) ", (unsigned int) jmp_pc[0], (unsigned int) sz);
    }
#endif
    return sz;
  default:
    PRDEBUG(" * opcode 0x%x (tbl=%d) unknown\n", (unsigned int) b, tb1);
    sz=0; *aCode=c_ill; break;
  }
  return sz;
}

int get_special_name(const char **np);

void decode_mangle(const char *n)
{
  if(!n || *n!='?') return;
  fprintf(stderr,"Decode ,%s'\n",n);
  n++;
  if(*n=='?') {
    int ret;
    n++;
    ret=get_special_name(&n);
    fprintf(stderr," %s (%s)\n",spna_names[ret],n);
  }
}

int get_special_name(const char **np)
{
  int ret = SPNA_UNKNOWN;
  const char *n = *np;
  if(*n=='_' && n[1]=='_') {
    n+=2;
    switch(n[0]) {
    case 'A': ret=SPNA_MANAGED_VECTOR_CONSTRUCTOR_ITER; break;
    case 'B': ret=SPNA_MANAGED_VECTOR_DESTRUCTOR_ITER; break;
    case 'C': ret=SPNA_EH_VECTOR_COPY_CONSTRUCTOR_ITER; break;
    case 'D': ret=SPNA_EH_VECTOR_VBASE_COPY_CONSTRUCTOR_ITER; break;
    default: fprintf(stderr,"SPNA __%c is unknown\n",n[0]); break;
    }
    n+=1;
  } else if(n[0]=='_') {
    n+=1;
    switch(n[0]) {
    case '0': ret=SPNA_OPER_DIVEQ; break;
    case '1': ret=SPNA_OPER_MODEQ; break;
    case '2': ret=SPNA_OPER_SHREQ; break;
    case '3': ret=SPNA_OPER_SHLEQ; break;
    case '4': ret=SPNA_OPER_ANDEQ; break;
    case '5': ret=SPNA_OPER_OREQ; break;
    case '6': ret=SPNA_OPER_XOREQ; break;
    case '7': ret=SPNA_VFTABLE; break;
    case '8': ret=SPNA_VBTABLE; break;
    case '9': ret=SPNA_VCALL; break;
    case 'A': ret=SPNA_TYPEOF; break;
    case 'B': ret=SPNA_LOCAL_STATIC_GUARD; break;
    case 'C': ret=SPNA_STRING; break;
    case 'D': ret=SPNA_VBASE_DESTRUCTOR; break;
    case 'E': ret=SPNA_VECTOR_DELETING_DESTRUCTOR; break;
    case 'F': ret=SPNA_VECTOR_DEFAULT_CONSTRUCTOR_CLOSURE; break;
    case 'G': ret=SPNA_SCALAR_DELETEING_DESTRUCTOR; break;
    case 'H': ret=SPNA_VECTOR_CONSTRUCTOR_ITER; break;
    case 'I': ret=SPNA_VECTOR_DESTRUCTOR_ITER; break;
    case 'J': ret=SPNA_VECTOR_VBASE_CONSTRUCTOR_ITER; break;
    case 'K': ret=SPNA_VIRTUAL_DISPLACEMENT_MAP; break;
    case 'L': ret=SPNA_EH_VECTOR_CONSTRUCTOR_ITER; break;
    case 'M': ret=SPNA_EH_VECTOR_DESTRUCTOR_ITER; break;
    case 'N': ret=SPNA_EH_VECTOR_VBASE_CONSTRUCTOR_ITER; break;
    case 'O': ret=SPNA_COPY_CONSTRUCTOR_CLOSURE; break;
    case 'P': ret=SPNA_UDT_RETURNING; break;
    case 'R':
      n+=1;
      switch(n[0]) {
      case '0': ret=SPNA_RTTI_TYPE_DESCRIPTOR; break;
      case '1': ret=SPNA_RTTI_BASE_CLASS_DESCRIPTOR; break;
      case '2': ret=SPNA_RTTI_CLASS_ARRAY; break;
      case '3': ret=SPNA_RTTI_CLASS_HIERACHY_DESCRIPTOR; break;
      case '4': ret=SPNA_RTTI_COMPLETE_OBJECT_LOCATOR; break;
      default:
	fprintf(stderr,"SPNA _R%c is unknown\n",n[0]); break;
      }
      break;
    case 'S': ret=SPNA_LOCAL_VFTABLE; break;
    case 'T': ret=SPNA_LOCAL_VFTABLE_CONSTRUCTOR_CLOSURE; break;
    case 'U': ret=SPNA_OPER_NEW_ARRAY; break;
    case 'V': ret=SPNA_OPER_DELETE_ARRAY; break;
    case 'X': ret=SPNA_PLACEMENT_DELETE_CLOSURE; break;
    case 'Y': ret=SPNA_PLACEMENT_DELETE_ARRAY_CLOSURE; break;
    default:
      fprintf(stderr,"SPNA _%c unknown\n",n[0]); break;
    }
    n+=1;
  } else {
    switch(n[0]) {
    case '0': ret=SPNA_CONSTRUCTOR; break;
    case '1': ret=SPNA_DESTRUCTOR; break;
    case '2': ret=SPNA_OPER_NEW; break;
    case '3': ret=SPNA_OPER_DELETE; break;
    case '4': ret=SPNA_OPER_EQ; break;
    case '5': ret=SPNA_OPER_SHR; break;
    case '6': ret=SPNA_OPER_SHL; break;
    case '7': ret=SPNA_OPER_NOT; break;
    case '8': ret=SPNA_OPER_EQEQ; break;
    case '9': ret=SPNA_OPER_NOTEQ; break;
    case 'A': ret=SPNA_OPER_ARRAY; break;
    case 'B': ret=SPNA_OPER_RETURNTYPE; break;
    case 'C': ret=SPNA_OPER_POINTTO; break;
    case 'D': ret=SPNA_OPER_MUL; break;
    case 'E': ret=SPNA_OPER_PLUSPLUS; break;
    case 'F': ret=SPNA_OPER_MINUSMINUS; break;
    case 'G': ret=SPNA_OPER_MINUS; break;
    case 'H': ret=SPNA_OPER_PLUS; break;
    case 'I': ret=SPNA_OPER_AND; break;
    case 'J': ret=SPNA_OPER_POINTTOSTAR; break;
    case 'K': ret=SPNA_OPER_DIV; break;
    case 'L': ret=SPNA_OPER_MOD; break;
    case 'M': ret=SPNA_OPER_LESS; break;
    case 'N': ret=SPNA_OPER_LESSEQ; break;
    case 'O': ret=SPNA_OPER_GREAT; break;
    case 'P': ret=SPNA_OPER_GREATEQ; break;
    case 'Q': ret=SPNA_OPER_COMMA; break;
    case 'R': ret=SPNA_OPER_CAST; break;
    case 'S': ret=SPNA_OPER_INV; break;
    case 'T': ret=SPNA_OPER_XOR; break;
    case 'U': ret=SPNA_OPER_OR; break;
    case 'V': ret=SPNA_OPER_ANDAND; break;
    case 'W': ret=SPNA_OPER_OROR; break;
    case 'X': ret=SPNA_OPER_MULEQ; break;
    case 'Y': ret=SPNA_OPER_PLUSEQ; break;
    case 'Z': ret=SPNA_OPER_MINUSEQ; break;
    default:
      fprintf(stderr,"SPNA %c unknown\n",n[0]); break;
    }
    n+=1;
  }
  *np=n;
  return ret;
}
