/* obj-res.c:  Dump out .prc compatible binary resource files from an object
 *
 * (c) 1996, 1997 Dionne & Associates
 * jeff@ryeham.ee.ryerson.ca
 *
 * This is Free Software, under the GNU Public Licence v2 or greater.
 *
 * Relocation added March 1997, Kresten Krab Thorup 
 * krab@california.daimi.aau.dk
 */

#include <stdio.h>
#include <fcntl.h>
#include <malloc.h>
#include <sys/types.h>
#include <netinet/in.h>

#define PARAMS(x) x
#include <bfd.h>
#include "prc.h"

#ifdef CYGWIN32
#define O_PLATFORM O_BINARY
#else
#define O_PLATFORM 0
#endif

static char res_ext[] = ".grc";

static unsigned char exec_magic[] = { 0, 0, 0, 1 };
static unsigned char code_zero[] = { 0, 0,    0, 0x28,
				     0, 0,    0,    0, 
				     0, 0,    0,    8,
				     0, 0,    0, 0x20,
				     0, 0, 0x3f, 0x3c,
				     0, 1, 0xa9, 0xf0 };

static unsigned char pref[] = { 00, 0x1e, 00, 00, 0x10, 00, 00, 00, 0x10, 00 };

static unsigned char *compress_data(unsigned char *raw,
				    int data_len,
				    int bss_len,
				    int *comp_len)
{
  unsigned char *dp;
  unsigned char *wp;
  int block_len;
  int count;
  int total = 0;

  wp = dp = malloc(data_len * 2 + 64);  /* data could in theory grow a lot */

  /* Thanks to Darrin Massena for this algorithm */

  *(unsigned int *)wp = htonl(data_len);
  wp += 4;
  total += 4;

  if ((data_len + bss_len) > 0x8ffc) {
    fprintf(stderr, "error: .data and .bss too large for data #0 resource\n");
    exit(1);
  }

  /* A5 offset */
  *(unsigned int *)wp = htonl(-(((data_len + bss_len) + 3) & 0x8ffc));

  wp += 4;
  total += 4;

  count = data_len;
  while(count) {

    block_len = (count < 0x80) ? count : 0x80;
    *(wp++) = (block_len - 1) | 0x80;
    total++;

    memcpy(wp, raw, block_len);

    wp +=    block_len;
    raw +=   block_len;
    total += block_len;
    count -= block_len;
  }

  /* 3 separator bytes, and 2 more A5 offsets, all 0, = 11 more bytes */

  memset(wp, 0, 11);
  total += 11;

  /* 6 longs of 0 for future compatibility with MW relocation extensions */

  memset(wp, 0, 6*4);
  total += 6*4;

  *comp_len = total;
  return dp;
}

main(int argc, char *argv[])
{
  int fd;
  bfd *bf;
  asection *s;
  char *ofile;

  unsigned char *data;
  unsigned char *comp;
  unsigned char *text;

  unsigned long data_len;
  unsigned long bss_len;
  unsigned long text_len;
  unsigned long comp_len;

  unsigned long data_vma;
  unsigned long bss_vma;
  unsigned long text_vma;

  int state = 0;

  if (argc != 2 && argc != 3 ) {
    fprintf(stderr, "Usage: %s [-reloc] bfd.file\n",argv[0]);
    exit(1);
  }

  if (!(bf = bfd_openr(argv[argc -1],0))) {
    fprintf (stderr,"Can't open %s\n",argv[argc -1]);
    exit(1);
  }

  if (bfd_check_format (bf, bfd_object) == 0) {
    printf("File is not an object file\n");
    exit(2);
  }

  s = bf->sections;

  while (s) {

#ifdef DEBUG
    printf ("Section %s VMA %p length %x relocs %d\n",
	    s->name,
	    s->vma,
	    s->_raw_size,
	    s->reloc_count);
#endif
    /* read in the interesting sections of the executable */

    if (!strcmp(s->name, ".text")) {
      state++;

      text_vma = s->vma;
      text_len = s->_raw_size + 4;  /* add space for that stupid 0 0 0 1 */
      text = malloc(text_len);

      if (bfd_get_section_contents(bf,
				   s, 
				   text + 4,
				   0,
				   s->_raw_size) == false) {
	fprintf(stderr, "read error section %s\n", s->name);
	exit(2);
      }

      memcpy(text, exec_magic, sizeof(exec_magic));
    }

    if (!strcmp(s->name, ".data")) {
      state++;

      data = malloc(s->_raw_size + 32); /* Allows for up to 32 byte align */
      data_vma = s->vma;

      if (bfd_get_section_contents(bf,
				   s, 
				   data,
				   0,
				   s->_raw_size) == false) {
	fprintf(stderr, "read error section %s\n", s->name);
	exit(2);
      }
    }

    if (!strcmp(s->name, ".bss")) {
      state++;

      bss_len = s->_raw_size;
      data_len = s->vma - data_vma;
      bss_vma = s->vma;
    }

    s = s->next;
  }

  if (state != 3) {
    fprintf(stderr, ".text, .data or .bss sections not found\n");
    exit(3);
  }

#ifdef DEBUG
  printf("0x%.4x .text, 0x%.4x .data, 0x%.4x bss\n",text_len,data_len,bss_len);
#endif

  comp = compress_data(data, data_len, bss_len, &comp_len);

  /* fill in the code #0 thing.  I still don't really know what to do here.
     Truth be known, I think it's mostly bogus. */

  *(unsigned int *)(&code_zero[4])=htonl(((data_len + bss_len) + 3) & 0x8ffc);

  ofile = malloc(strlen(argv[1]) + 15); /* 15 to add prefixes and suffixes */

  strcpy(ofile, "code0000.");
  strcat(ofile, argv[1]);
  strcat(ofile, res_ext);

  if ((fd = open (ofile, O_WRONLY | O_PLATFORM | O_CREAT | O_TRUNC, 0644)) < 0) {
    fprintf (stderr, "Can't open output file %s\n", ofile);
    exit(4);
  }

  write(fd, code_zero, sizeof(code_zero));
  close(fd);

  strcpy(ofile, "data0000.");
  strcat(ofile, argv[1]);
  strcat(ofile, res_ext);

  if ((fd = open (ofile, O_WRONLY | O_PLATFORM | O_CREAT | O_TRUNC, 0644)) < 0) {
    fprintf (stderr, "Can't open output file %s\n", ofile);
    exit(4);
  }

  write(fd, comp, comp_len);
  close(fd);

  strcpy(ofile, "code0001.");
  strcat(ofile, argv[1]);
  strcat(ofile, res_ext);

  if ((fd = open (ofile, O_WRONLY | O_PLATFORM | O_CREAT | O_TRUNC, 0644)) < 0) {
    fprintf (stderr, "Can't open output file %s\n", ofile);
    exit(4);
  }

  write(fd, text, text_len);
  close(fd);

  strcpy(ofile, "pref0000.");
  strcat(ofile, argv[1]);
  strcat(ofile, res_ext);

  if ((fd = open (ofile, O_WRONLY | O_PLATFORM | O_CREAT | O_TRUNC, 0644)) < 0) {
    fprintf (stderr, "Can't open output file %s\n", ofile);
    exit(4);
  }

  write(fd, pref, 10);
  close(fd);

  /* generate data-relocations */
  if (argc == 3) {
    output_relocs (bf, argv[1]);
  }
  exit(0);
}



/* RELOCS STUFF STARTS HERE */
struct pilot_reloc {
  unsigned char  type;
  unsigned char  section;  
  unsigned short offset;
           long  value ;
} ;

#define TEXT_SECTION 't'
#define DATA_SECTION 'd'
#define BSS_SECTION  'b'

#define RELOC_ABS_32       0XBe


asymbol**
get_symbols (bfd *abfd)
{
  long storage_needed;
  asymbol **symbol_table;
  long number_of_symbols;
  long i;
  
  storage_needed = bfd_get_symtab_upper_bound (abfd);
	  
  if (storage_needed < 0)
    abort ();
      
  if (storage_needed == 0) {
    return ;
  }

  symbol_table = (asymbol **) malloc (storage_needed);

  number_of_symbols = bfd_canonicalize_symtab (abfd, symbol_table);
  
  if (number_of_symbols < 0) 
    abort ();

  return symbol_table;
}
     

int output_relocs (bfd *input_bfd, char *name)
{
  int fd;
  char *ofile = alloca(strlen(name) + 15);  
  asymbol **symbols = get_symbols (input_bfd);
  asection *data_section = bfd_get_section_by_name (input_bfd, ".data");
  asection *bss_section = bfd_get_section_by_name (input_bfd, ".bss");
  asection *text_section = bfd_get_section_by_name (input_bfd, ".text");

  /* we only support relocations in the data section */
  asection *input_section = data_section;
  long reloc_size = bfd_get_reloc_upper_bound (input_bfd, input_section);
  arelent **reloc_vector = NULL;
  long reloc_count, count;

  struct pilot_reloc *pilot_relocs = (struct pilot_reloc*)malloc ((input_section->reloc_count + 1)
					  * sizeof (struct pilot_reloc));
  long num_prelocs = 0;

  if (reloc_size < 0)
    return;

  reloc_vector = (arelent **) malloc ((size_t) reloc_size);
  if (reloc_vector == NULL && reloc_size != 0)
    abort ();

  reloc_count = bfd_canonicalize_reloc (input_bfd,
					input_section,
					reloc_vector,
					symbols);
  if (reloc_count < 0)
    abort ();

  for (count = 0; count < reloc_count; count++)
    {
      arelent *r = &(*reloc_vector)[count];
      asymbol *s = *r->sym_ptr_ptr;
      struct pilot_reloc  *p = &pilot_relocs[num_prelocs];

#ifdef DEBUG
      printf ("DATA %s: ", r->howto->name);
      printf ("addr=0x%x ", r->address);
      printf ("sym=%s+%x", s->name, s->value);
      printf ("\n");
#endif

      if (!strncmp (r->howto->name, "32", 2))
	{
	  pilot_relocs[num_prelocs].type = RELOC_ABS_32;
	  num_prelocs += 1;
	}
      else
	{
	  fprintf (stderr, "warning: skipping relocation");
	  fprintf (stderr, "DATA %s: ", r->howto->name);
	  fprintf (stderr, "addr=0x%x ", r->address);
	  fprintf (stderr, "sym=%s+%x", s->name, s->value);
	  fprintf (stderr, "\n");
	  continue;
	}

      /* offset into data section... */
      p->offset = htons (r->address - data_section->vma); 

      /* store the value of the symbol */
      p->value   = htonl (s->value);

      /* store value of... */
      if (s->section == text_section)
	p->section = TEXT_SECTION;

      else if (s->section == data_section)
	p->section = DATA_SECTION;

      else if (s->section == bss_section)
	p->section = BSS_SECTION;

      else
	abort ();
    }

  strcpy(ofile, "rloc0000.");
  strcat(ofile, name);
  strcat(ofile, res_ext);

  if ((fd = open (ofile, O_WRONLY | O_PLATFORM | O_CREAT | O_TRUNC, 0644)) < 0) {
    fprintf (stderr, "Can't open output file %s\n", ofile);
    exit(4);
  }

  {
    short size = htons ((short) num_prelocs);

    write(fd, &size, 2);

    write(fd, &pilot_relocs[0], sizeof (struct pilot_reloc) * num_prelocs);
    close(fd);
  }
}

