/* class.cpp */
/*
   Java Decompiler 
   Copyright (c) 1994-2003, Pete Ryland.
   Distributed under the GNU GPL Version 2.
   This package is available from http://pdr.cx/hbd/
*/

#include <string.h>
#include <ctype.h>
#include "general.h"
#include "options.h"
#include "cp.h"
#include "access.h"
#include "field.h"
#include "exp.h"
#include "method.h"
#include "version.h"
#include "class.h"
#include "file.h"
#include "err.h"
#include "consts.h"

#define JAVA_CLASSFILE_MAGIC              0xCafeBabeL

char *progname;

Classfile::Classfile(int argc, char **argv) {
  functoinsert = 0;
  outfile = stdout; infile = stdin;
  progname = *argv++;
  if (strcmp(progname + strlen(progname) - 3, "hbt") == 0) {
    for (; (--argc) && (**argv == '-'); argv++) {
      options = (CL_Options)0;
      switch (toupper((*argv)[1])) {
        case 'D': *(int *)&options |= (int)OPT_DEBUG; break;
        case 'I': functoinsert = &((*argv)[2]); break;
        default:
          fprintf(stderr, "Unknown flag: %s\n", argv[1]);
          fatalerror(COMMAND_LINE_ERR_HBT, progname);
      }
    }
    char *tmpstr;
    switch (argc) {
      case 1:
        tmpstr = new char[strlen(argv[0])+5];
        strcpy(tmpstr, argv[0]);
        strcat(tmpstr, ".bak");
        rename(argv[0], tmpstr);
        if ((infile = fopen(tmpstr, "rb"))==NULL) {
          fprintf(stderr, "Could not open file %s\n", argv[0]);
          goto defaultcase_hbt;
        }
        if ((outfile = fopen(argv[0], "wb"))==NULL) {
          fprintf(stderr, "Could not open file %s\n", argv[1]);
          goto defaultcase_hbt;
        }
        delete tmpstr;
        break;
      default: defaultcase_hbt:
        fatalerror(COMMAND_LINE_ERR_HBT, progname);
    }
    if (functoinsert == 0)
      fatalerror(COMMAND_LINE_ERR_HBT, progname);
    outfile_pos = 0;
  } else {
    for (; (--argc) && (**argv == '-'); argv++) {
      options = (CL_Options)0;
      switch (toupper((*argv)[1])) {
        case 'O': *(int *)&options |= (int)OPT_DECOMPILE_OFF; break;
        case 'D': *(int *)&options |= (int)OPT_DEBUG; break;
        default:
          fprintf(stderr, "Unknown flag: %s\n", argv[1]);
          fatalerror(COMMAND_LINE_ERR_HBD, progname);
      }
    }
    switch (argc) {
      case 2:
        if ((outfile = fopen(argv[1], "wb"))==NULL) {
          fprintf(stderr, "Could not open file %s\n", argv[1]);
          goto defaultcase_hbd;
        }
      case 1:
        if ((infile = fopen(argv[0], "rb"))==NULL) {
          fprintf(stderr, "Could not open file %s\n", argv[0]);
          goto defaultcase_hbd;
        }
        break;
      default: defaultcase_hbd:
       fatalerror(COMMAND_LINE_ERR_HBD, progname);
    }
  }
  infile_pos = 0;
}

void Classfile::read() {
  u16 numimports;

  if (get32(infile, &infile_pos) != JAVA_CLASSFILE_MAGIC) fatalerror(NOT_A_CLASS_ERR);

  version.read(this);
  fprintf(stderr, "Classfile version %d.%d\n", version.major_version, version.minor_version);

  imports_count = 0;
  cp.read(this, &imports_count);
  access_flags = get16(infile, &infile_pos);

  this_class = get16(infile, &infile_pos);
  super_class = get16(infile, &infile_pos);
  if (((interfaces = new u16[interfaces_count = get16(infile, &infile_pos)]) == 0) && interfaces_count) memerr();
  u16 i,j;
  for (j = 0, i = interfaces_count; i--;) {
    interfaces[j++] = get16(infile, &infile_pos);
  }
  if (((fields = new field_info_ptr[fields_count = get16(infile, &infile_pos)]) == 0) && fields_count) memerr();
  for (j = 0, i = fields_count; i--;) {
    field_info *fip = fields[j++] = new field_info;
    if (!fip) memerr();
    field_info &fi = *fip;
    fi.isconstant = 0;
    fi.access_flags = get16(infile, &infile_pos);
    fi.name = cp[get16(infile, &infile_pos)]->chp;
    fi.sig = cp[get16(infile, &infile_pos)]->chp;
    for (int l = get16(infile, &infile_pos); l--;) {
      u16 attribute_name_index = get16(infile, &infile_pos);
      u32 attribute_size = get32(infile, &infile_pos);
      char *attribute_name = cp[attribute_name_index]->chp;
      if (!strcmp(attribute_name,"ConstantValue")) {
        if (attribute_size != 2) {
          fprintf(stderr, "Bad size on ConstantValue Attribute - should be 2!\n");
          exit(1);
        } else {
          fi.isconstant = 1;
          fi.constval_index = get16(infile, &infile_pos);
        }
      } else {
        fprintf(stderr, "Skipping Unknown Field Attribute: %s (size %ld)\n", attribute_name, attribute_size);
        for (u32 m = attribute_size; m--;) get8(infile, &infile_pos);
      }
    }
  }
  if (((methods = new method_info_ptr[methods_count = get16(infile, &infile_pos)]) == 0) && methods_count) memerr();
  imports_count += methods_count;
  numimports = 0;
  if ((imports = new char_ptr[imports_count]) == 0) memerr();
  char *tmpstr1, *tmpstr2, *tmpstr;
  int package_name_length;
  tmpstr = cp(this_class)->chp;
  if ((tmpstr1 = strchr(tmpstr,'/')) != 0) {
    int l;
    while (tmpstr1) {
      l = tmpstr1 - tmpstr;
      tmpstr1 = strchr(tmpstr1 + 1, '/');
    }
    if ((package_name = new char[l+1]) == 0) memerr();
    strncpy(package_name, tmpstr, l);
    package_name[l] = '\0';
    if ((this_class_name = new char[strlen(tmpstr + l + 1) + 1]) == 0) memerr();
    strcpy(this_class_name, cp(this_class)->chp = tmpstr + l + 1);
    tmpstr = package_name;
    package_name_length = strlen(tmpstr);
    while ((tmpstr = strchr(tmpstr,'/')) != 0) *tmpstr++ = '.';
  } else {
    package_name = 0;
    package_name_length = 0;
    if ((this_class_name = new char[strlen(tmpstr) + 1]) == 0) memerr();
    strcpy(this_class_name, tmpstr);
  }
  for (i16 l = cp.count(); --l >= 0;) {
    cp_info tmpcpi = *(cp[l]);
    if (tmpcpi.tag == CONSTANT_Class) {
      char **chap = &(cp(l)->chp);
      tmpstr = *chap;
      if (!strncmp(tmpstr, "java/lang/", 10)) *chap = tmpstr + 10;
      else while ((tmpstr = strchr(tmpstr,'/')) != 0) *tmpstr++ = '.';
      tmpstr = *chap;
      if (package_name && !strncmp(tmpstr, package_name, package_name_length)) *chap = tmpstr += package_name_length + 1;
      if ((numimports!=imports_count)&&(tmpstr2 = strrchr(tmpstr,'.')) != 0) {
        imports[numimports++] = tmpstr;
        for (int tint = numimports - 2; tint>=0; tint--) {
          if (!strcmp(imports[tint], tmpstr)) {
            --numimports;
            break;
          }
        }
        *chap = tmpstr2 + 1;
      }
    } else if(tmpcpi.tag == CONSTANT_NameAndType) {
      //      int tmpindex;
      tmpstr = cp[/*tmpindex = */((NameAndType*)tmpcpi.p)->signature_index]->chp;
        char *copytmpstr = tmpstr2 = strdup(tmpstr); if (!copytmpstr) memerr();
      char *srcstr = tmpstr2, *deststr = tmpstr, *tmpstr3, *tmpstr4;
      while ((*deststr++ = *srcstr++) != '\0') {
        if (*(srcstr-1) == 'L') {
          tmpstr3 = strchr(srcstr, ';'); if (!tmpstr3) fatalerror(UNKNOWN_ERR);
          if (!strncmp(srcstr, "java/lang/", 10)) srcstr += 10;
          else {
            tmpstr2 = srcstr;
            while (((tmpstr2 = strchr(tmpstr2,'/')) != 0)&&(tmpstr2 < tmpstr3)) *tmpstr2++ = '.';
          }
          if (package_name && !strncmp(srcstr, package_name, package_name_length)) srcstr += package_name_length + 1;
          if (numimports!=imports_count && (tmpstr4 = strchr(srcstr, '.')) != 0 && tmpstr4 < tmpstr3) {
            while (tmpstr4 != 0 && tmpstr4 < tmpstr3) { tmpstr2 = tmpstr4+1; tmpstr4 = strchr(tmpstr2,'.'); }
            int tint = tmpstr3 - srcstr;
            char *tstr = imports[numimports++] = new char[tint+1];
            if (!tstr) memerr();
            tstr = strncpy(tstr, srcstr, tint);
            tstr[tint] = '\0';
            for (tint = numimports - 2; tint>=0; tint--) {
              if (!strcmp(imports[tint], tstr)) {
                --numimports;
                delete tstr;
                break;
              }
            }
            srcstr = tmpstr2;
          }
          while ((*deststr++ = *srcstr++) != ';') ;
        }
      }
    }
  }
  for (j = 0, i = methods_count; i--;) {
    method_info *mip = methods[j++] = new method_info;
    if (!mip) memerr();
    method_info &mi = *mip;
    mi.access_flags = get16(infile, &infile_pos);
    char *tmpstr;
    mi.name = cp[get16(infile, &infile_pos)]->chp;
    mi.sig = cp[get16(infile, &infile_pos)]->chp;

    //    int tmpindex;
    tmpstr = mi.sig;
    char *copytmpstr = tmpstr2 = strdup(tmpstr); if (!copytmpstr) memerr();
    char *srcstr = tmpstr2, *deststr = tmpstr, *tmpstr3, *tmpstr4;
    while ((*deststr++ = *srcstr++) != '\0') {
      if (*(srcstr-1) == 'L') {
        tmpstr3 = strchr(srcstr, ';'); if (!tmpstr3) fatalerror(UNKNOWN_ERR);
        if (!strncmp(srcstr, "java/lang/", 10)) srcstr += 10;
        else {
          tmpstr2 = srcstr;
          while (((tmpstr2 = strchr(tmpstr2,'/')) != 0)&&(tmpstr2 < tmpstr3)) *tmpstr2++ = '.';
        }
        if (package_name && !strncmp(srcstr, package_name, package_name_length)) srcstr += package_name_length + 1;
        if (numimports!=imports_count && (tmpstr4 = strchr(srcstr, '.')) != 0 && tmpstr4 < tmpstr3) {
          while (tmpstr4 != 0 && tmpstr4 < tmpstr3) { tmpstr2 = tmpstr4+1; tmpstr4 = strchr(tmpstr2,'.'); }
          int tint = tmpstr3 - srcstr;
          char *tstr = imports[numimports++] = new char[tint+1];
          if (!tstr) memerr();
          tstr = strncpy(tstr, srcstr, tint);
          tstr[tint] = '\0';
          for (tint = numimports - 2; tint>=0; tint--) {
            if (!strcmp(imports[tint], tstr)) {
              --numimports;
              delete tstr;
              break;
            }
          }
          srcstr = tmpstr2;
        }
      }
    }
    mi.num_throws = 0;
    mi.local_variable_table_length = 0;
    mi.line_number_table_length = 0;
    for (int l = get16(infile, &infile_pos); l--;) {
      u16 attribute_name_index = get16(infile, &infile_pos);
      u32 attribute_size = get32(infile, &infile_pos);
      char *attribute_name = cp[attribute_name_index]->chp;
      if (!strcmp(attribute_name,"Code")) {
        mi.max_stack = (u8)get16(infile, &infile_pos);
        mi.max_locals = (u8)get16(infile, &infile_pos);
        if ((mi.code = new u8[mi.code_length = get32(infile, &infile_pos)]) == 0) memerr();
        getstr(mi.code, mi.code_length, infile);
        if (((mi.exception_table = new ExceptionTableEntry[mi.exception_table_length = get16(infile, &infile_pos)]) == 0) && mi.exception_table_length) memerr();
        for (u16 n = 0, m = mi.exception_table_length; m--;) {
          mi.exception_table[n].tag = TRY;
          mi.exception_table[n].start_pc = get16(infile, &infile_pos);
          mi.exception_table[n].end_pc = get16(infile, &infile_pos);
          mi.exception_table[n].handler_pc = get16(infile, &infile_pos);
          mi.exception_table[n++].catch_type = get16(infile, &infile_pos);
        }
        //        getstr(mi.exception_table, mi.exception_table_length << 3, infile);
        for (int l2 = get16(infile, &infile_pos); l2--;) {
          u16 attribute_name_index2 = get16(infile, &infile_pos);
          u32 attribute_size2 = get32(infile, &infile_pos);
          char *attribute_name2 = cp[attribute_name_index2]->chp;
          if (!strcmp(attribute_name2,"LineNumberTable")) {
            if ((mi.line_number_table = new LineNumberTableEntry[mi.line_number_table_length = get16(infile, &infile_pos)]) == 0) memerr();
            getstr(mi.line_number_table, mi.line_number_table_length << 2, infile);
          } else if (!strcmp(attribute_name2,"LocalVariableTable")) {
            if ((mi.local_variable_table = new LocalVariableTableEntry[mi.local_variable_table_length = get16(infile, &infile_pos)]) == 0) memerr();
            getstr(mi.local_variable_table, mi.local_variable_table_length * 10, infile);
            if ((mi.local_names = new char*[2*mi.local_variable_table_length]) == 0) memerr();
            int o;
            for (o = mi.local_variable_table_length; o--;) {
              char *tmpstr = cp[mi.local_variable_table[o].name_index]->chp;
              if ((mi.local_names[mi.local_variable_table[o].slot] = new char[strlen(tmpstr) + 1]) == 0) memerr();
              strcpy(mi.local_names[mi.local_variable_table[o].slot], tmpstr);
            }
            if ((mi.local_sigs = new char*[2*mi.local_variable_table_length]) == 0) memerr();
            for (o = mi.local_variable_table_length; o--;) {
              char *tmpstr = cp[mi.local_variable_table[o].signature_index]->chp;
              if ((mi.local_sigs[mi.local_variable_table[o].slot] = new char[strlen(tmpstr) + 1]) == 0) memerr();
              strcpy(mi.local_sigs[mi.local_variable_table[o].slot], tmpstr);
            }
          } else {
            fprintf(stderr, "Skipping Unknown Code Attribute: %s (size %ld)\n", attribute_name2, attribute_size2);
            for (int m = attribute_size2; m--;) get8(infile, &infile_pos);
          }
        }
      } else if (!strcmp(attribute_name,"Exceptions")) {
        int *tmpintptr;
        if ((tmpintptr = mi.throws = new int[mi.num_throws = get16(infile, &infile_pos)]) == 0) memerr();
        for (int m = mi.num_throws;m--;) *tmpintptr++ = get16(infile, &infile_pos); //*tmpstr++ = constant_pool[constant_pool[get16(infile)].i].cp;
      } else {
        fprintf(stderr, "Skipping Unknown Method Attribute: %s (size %ld)\n", attribute_name, attribute_size);
        //      char *ti = new int[attribute_size], *tip = ti;
        for (unsigned int m = 0; m++!=attribute_size;) printf("%02x%c", /**tip++ =*/ get8(infile, &infile_pos), (m%8)?' ':(m%16?'\n':'\t'));
        printf("\n");
        //      int tc = ti[0]<<8+ti[1]
      }
    }
  }
  imports_count = numimports;
  for (j = 0, i = get16(infile, &infile_pos); i--;) {
    u16 attribute_name_index = get16(infile, &infile_pos);
    u32 attribute_size = get32(infile, &infile_pos);
    char *attribute_name = cp[attribute_name_index]->chp;
    if (!strcmp(attribute_name,"SourceFile")) {
      if (attribute_size != 2) {
        fprintf(stderr, "Bad size on SourceFile Attribute - should be 2!\n");
        exit(1);
      } else {
        char *tmpstr = cp[get16(infile, &infile_pos)]->chp;
        if ((source_name = new char[strlen(tmpstr) + 1]) == 0) memerr();
        strcpy(source_name, tmpstr);
      }
    } else {
      fprintf(stderr, "Skipping Unknown Attribute: %s (size %ld)\n", attribute_name, attribute_size);
      for (u32 k = attribute_size; k--;) get8(infile, &infile_pos);
    }
  }
}

void Classfile::print() {
  int i,j;
  fprintf(stderr, "Compiled from %s\n", source_name);
  fprintf(outfile, "/*\n** Compiled from %s - COPYRIGHT UNKNOWN.\n**\n"
      "** Decompiled using the HomeBrew Decompiler\n"
      "** Copyright (c) 1994-2003 Widget (aka Pete Ryland).\n"
      "** Available under GPL from http://pdr.cx/hbd/\n*/\n\n", source_name);
  if (package_name) fprintf(outfile, "package %s;\n\n", package_name);
  char **strptr = imports;
  for (i = imports_count; i--;) {
    fprintf(outfile, "import %s;\n", *strptr++);
  }
  fprintf(outfile, "\n");
  char *tmpstr = new char[access_flags.strlen() + 1];
  fprintf(outfile, "%sclass %s ", access_flags.toString(tmpstr), this_class_name);
  delete tmpstr;
  if (super_class) {
    if (!strcmp(tmpstr = cp(super_class)->chp, "Object")) {
      super_class_name = "Object";
    } else {
      if ((super_class_name = new char[strlen(tmpstr) + 1]) == 0) memerr();
      strcpy(super_class_name, tmpstr);
      fprintf(outfile, "extends %s ", super_class_name);
    }
  }
  if (interfaces_count) {
    fprintf(outfile, "implements ");
    for (j = 0, i = interfaces_count - 1; i--;) {
      fprintf(outfile, "%s, ", cp(interfaces[j])->chp);
    }
    fprintf(outfile, "%s ", cp(interfaces[j])->chp);
  }
  fprintf(outfile, "{");
  for (j = 0, i = fields_count; i--;) {
    field_info &fi = *(fields[j++]);
    tmpstr = new char[fi.access_flags.strlen() + 1];
    fprintf(outfile, "\n  %s", fi.access_flags.toString(tmpstr));
    delete tmpstr;
    tmpstr = fi.sig;
    printsigname(this, outfile, tmpstr, fi.name, 0);
    if (fi.isconstant) {
      fprintf(outfile, " = ");
      char *chptr = fi.sig;
      switch(*chptr++) {
        case SIGNATURE_INT:
          fprintf(outfile, "0x%lX", cp[fi.constval_index]->i);
          break;
        case SIGNATURE_LONG:
          if(cp[fi.constval_index]->i) {
            fprintf(outfile, "0x%lX%08lXL", cp[fi.constval_index]->i,
                cp[(u16)((i16)fi.constval_index + 1)]->i);
          } else {
            fprintf(outfile, "0x%lXL", cp[(u16)((i16)fi.constval_index + 1)]->i);
          }
          break;
        case SIGNATURE_FLOAT:
          fprintf(outfile, "%#.100Gf", cp[fi.constval_index]->f);
          break;
        case SIGNATURE_DOUBLE:
          fprintf(outfile, "%#.100Gd",(float)*(double*)(cp[fi.constval_index]->i));
          break;
        default:
          fprintf(stderr, "Bad type for constant\n");
      }
    }
    fprintf(outfile, ";");
  }
  for (j = 0, i = methods_count; i--;) {
    if (decompileblock(this, methods[j++])) fprintf(outfile, "/* Decompilation Error.  Continuing... */");
  }
  fprintf(outfile, "\n}");
  return;
}
