#include <stdio.h>
#include <stdlib.h>
#include "ld.h"

ldLibrary *ldAddLibrary(const char *lib) {
  int        i        = 0x0;
  int        x        = 0x0;
  int        rel      = 0x0;
  uInt32    *reMap    = 0x0;
  char      *newLoc   = 0x0;
  FILE      *linkerFd = 0x0;
  ldLibrary *tmpLib   = 0x0;

  tmpLib = (ldLibrary *)malloc(sizeof(ldLibrary));

  if (tmpLib->output == 0x0) {
    linkerFd = fopen(lib,"rb");
    if (linkerFd == 0x0) {
      printf("Cant Open Library: %s\n",lib);
      exit(-1);
      }
    tmpLib->output = (char *)malloc((linkerFd->size+0x4000));
    sprintf(tmpLib->name,lib);
    }

  if (tmpLib->linkerHeader == 0x0) {
    fseek(linkerFd,0x0,0x0);
    tmpLib->linkerHeader = (elfHeader *)malloc(sizeof(elfHeader));
    fread(tmpLib->linkerHeader,sizeof(elfHeader),1,linkerFd);
    }

  if (tmpLib->linkerProgramHeader == 0x0) {
    tmpLib->linkerProgramHeader = (elfProgramHeader *)malloc(sizeof(elfProgramHeader)*tmpLib->linkerHeader->ePhnum);
    fseek(linkerFd,tmpLib->linkerHeader->ePhoff,0);
    fread(tmpLib->linkerProgramHeader,sizeof(elfProgramHeader),tmpLib->linkerHeader->ePhnum,linkerFd);

    for (i=0;i<tmpLib->linkerHeader->ePhnum;i++) {
      switch (tmpLib->linkerProgramHeader[i].phType) {
        case 1:
        case 2:
          newLoc = (char *)tmpLib->linkerProgramHeader[i].phVaddr + (uInt32)tmpLib->output;
          fseek(linkerFd,tmpLib->linkerProgramHeader[i].phOffset,0);
          fread(newLoc,tmpLib->linkerProgramHeader[i].phFilesz,1,linkerFd);
          break;
        default:
          printf("Unhandled Header\n");
          break;
        }
      }
    }

  if (tmpLib->linkerSectionHeader == 0x0) {
    tmpLib->linkerSectionHeader = (elfSectionHeader *)malloc(sizeof(elfSectionHeader)*tmpLib->linkerHeader->eShnum);
    fseek(linkerFd,tmpLib->linkerHeader->eShoff,0);
    fread(tmpLib->linkerSectionHeader,sizeof(elfSectionHeader),tmpLib->linkerHeader->eShnum,linkerFd);

  if (tmpLib->linkerShStr == 0x0) {
    tmpLib->linkerShStr = (char *)malloc(tmpLib->linkerSectionHeader[tmpLib->linkerHeader->eShstrndx].shSize);
    fseek(linkerFd,tmpLib->linkerSectionHeader[tmpLib->linkerHeader->eShstrndx].shOffset,0);
    fread(tmpLib->linkerShStr,tmpLib->linkerSectionHeader[tmpLib->linkerHeader->eShstrndx].shSize,1,linkerFd);
    }

  for (i=0x0;i<tmpLib->linkerHeader->eShnum;i++) {
    switch (tmpLib->linkerSectionHeader[i].shType) {
     case 3:
        if (!strcmp((tmpLib->linkerShStr + tmpLib->linkerSectionHeader[i].shName),".dynstr")) {
          if (tmpLib->linkerDynStr == 0x0) {
            tmpLib->linkerDynStr = (char *)malloc(tmpLib->linkerSectionHeader[i].shSize);
            fseek(linkerFd,tmpLib->linkerSectionHeader[i].shOffset,0);
            fread(tmpLib->linkerDynStr,tmpLib->linkerSectionHeader[i].shSize,1,linkerFd);
            }
          }
        break;
      case 9:
        tmpLib->linkerElfRel = (elfPltInfo *)malloc(tmpLib->linkerSectionHeader[i].shSize);
        fseek(linkerFd,tmpLib->linkerSectionHeader[i].shOffset,0x0);
        fread(tmpLib->linkerElfRel,tmpLib->linkerSectionHeader[i].shSize,1,linkerFd);

        for (x=0x0;x<tmpLib->linkerSectionHeader[i].shSize/sizeof(elfPltInfo);x++) {
          rel = ELF32_R_SYM(tmpLib->linkerElfRel[x].pltInfo);
          reMap = (uInt32 *)((uInt32)tmpLib->output + tmpLib->linkerElfRel[x].pltOffset);
          switch (ELF32_R_TYPE(tmpLib->linkerElfRel[x].pltInfo)) {
            case R_386_32:
              *reMap += ((uInt32)tmpLib->output + tmpLib->linkerRelSymTab[rel].dynValue);
              break;
            case R_386_PC32:
              *reMap += ((uInt32)tmpLib->output + tmpLib->linkerRelSymTab[rel].dynValue) - (uInt32)reMap;
              break;
            case R_386_RELATIVE:
              *reMap += (uInt32)tmpLib->output;
              break;
            default:
              printf("Unhandled sym\n");
              break;
            }
          }
        free(tmpLib->linkerElfRel);
        break;
      case 11:
        if (tmpLib->linkerRelSymTab == 0x0) {
          tmpLib->linkerRelSymTab = (elfDynSym *)malloc(tmpLib->linkerSectionHeader[i].shSize);
          fseek(linkerFd,tmpLib->linkerSectionHeader[i].shOffset,0);
          fread(tmpLib->linkerRelSymTab,tmpLib->linkerSectionHeader[i].shSize,1,linkerFd);
          tmpLib->sym = i;
          }
        break;
      }
    }
    }

  libs->prev    = tmpLib;
  tmpLib->prev  = 0x0;
  tmpLib->next  = libs;
  libs          = tmpLib;

  if (linkerFd) {
    fclose(linkerFd);
    }
  
  return(tmpLib);
  }
