/*
 * rdfdump.c - dump RDOFF file header.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rdoff.h"

FILE *infile;

long translatelong(long in) {		/* translate from little endian to
					   local representation */
  long r;
  unsigned char *i;

  i = (unsigned char *)&in;
  r = i[3];
  r = (r << 8) + i[2];
  r = (r << 8) + i[1];
  r = (r << 8) + *i;

  return r;
}
  
int16 translateshort(int16 in) {
  int r;
  unsigned char *i;

  i = (unsigned char *)&in;
  r = (i[1] << 8) + *i;

  return r;
}

void print_header(long length, int rdf_version)
{
    char buf[129],t,l,s,flags;
  unsigned char reclen;
  long o,ll;
  int16 rs;

  while (length > 0) {
    fread(&t,1,1,infile);
    if (rdf_version >= 2) {
	fread(&reclen,1,1,infile);
    }
    switch(t) {
	    case RDFREC_GENERIC:		/* generic record */
	    	printf("  generic record (length=%d)\n", (int)reclen);
		fseek(infile, reclen, SEEK_CUR);
		break;
    
	    case RDFREC_RELOC:			/* relocation record */
	    case RDFREC_SEGRELOC:		/* segment relocation */
      fread(&s,1,1,infile);
      fread(&o,4,1,infile);
      fread(&l,1,1,infile);
      fread(&rs,2,1,infile);
      printf("  %s: location (%04x:%08lx), length %d, "
	     "referred seg %04x\n", t == 1 ? "relocation" : "seg relocation",
	     (int)s,translatelong(o),(int)l,
	     translateshort(rs));
      if (rdf_version >= 2 && reclen != 8)
	  printf("    warning: reclen != 8\n");
      if (rdf_version == 1) length -= 9;
      if (rdf_version == 1 && t == 6)
	  printf("    warning: seg relocation not supported in RDOFF1\n");
      break;
      
	    case RDFREC_IMPORT:			/* import record */
	    case RDFREC_FARIMPORT:		/* import far symbol */
      fread(&rs,2,1,infile);
      ll = 0;

      if (rdf_version == 1) {
	  do {
	      fread(&buf[ll],1,1,infile);
	  } while (buf[ll++]);
		} else {
	  for (;ll < reclen - 2; ll++)
	      fread(&buf[ll],1,1,infile);
      }

      printf("  %simport: segment %04x = %s\n",t == 7 ? "far " : "",
	     translateshort(rs),buf);
      if (rdf_version == 1) length -= ll + 3;
      if (rdf_version == 1 && t == 7)
	  printf ("    warning: far import not supported in RDOFF1\n");
      break;
      
	    case RDFREC_GLOBAL:			/* export record */
      fread(&flags,1,1,infile);
      fread(&s,1,1,infile);
      fread(&o,4,1,infile);
      ll = 0;

      if (rdf_version == 1) {
	  do {
	      fread(&buf[ll],1,1,infile);
	  } while (buf[ll++]);
		} else {
	  for (; ll < reclen - 6; ll ++)
	      fread(&buf[ll],1,1,infile);
      }
      if (flags & SYM_GLOBAL)
        printf("  export");
      else
        printf("  global");
      if (flags & SYM_FUNCTION) printf(" proc");
      if (flags & SYM_DATA) printf(" data");
      printf(": (%04x:%08lx) = %s\n",(int)s,translatelong(o),buf);
      if (rdf_version == 1) length -= ll + 6;
      break;
      
	    case RDFREC_DLL:			/* DLL and Module records */
	    case RDFREC_MODNAME:
      ll = 0;

      if (rdf_version == 1) {
	  do {
	      fread(&buf[ll],1,1,infile);
	  } while (buf[ll++]);
		} else {
	  for (; ll < reclen; ll++)
	      fread(&buf[ll],1,1,infile);
      }
      if (t==4) printf("  dll: %s\n",buf);
      else printf("  module: %s\n",buf);
      if (rdf_version == 1) length -= ll + 1;
      break;
		
	    case RDFREC_BSS:			/* BSS reservation */
      fread(&ll,4,1,infile);
      printf("  bss reservation: %08lx bytes\n",translatelong(ll));
      if (rdf_version == 1) length -= 5;
      if (rdf_version > 1 && reclen != 4)
	  printf("    warning: reclen != 4\n");
      break;
      
 	    case RDFREC_COMMON: {
		unsigned short seg, align;
		unsigned long size;
		
		fread(&seg, 2, 1, infile);
		fread(&size, 4, 1, infile);
		fread(&align, 2, 1, infile);
		for (ll = 0; ll < reclen - 8; ll++)
			fread(buf+ll, 1, 1, infile);
		printf("  common: segment %04x = %s, %ld:%d\n", translateshort(seg),
		       buf, translatelong(size), translateshort(align));
      break;  
	    }

    default:
		printf("  unrecognized record (type %d", (int)t);
      if (rdf_version > 1) {
      	printf(", length %d",(int)reclen);
	fseek(infile,reclen,SEEK_CUR);
		} else length --;
      printf(")\n");
    }
    if (rdf_version != 1) length -= 2 + reclen;
  }
}

char * knowntypes[8] = {"NULL", "text", "data", "object comment",
			"linked comment", "loader comment",
			"symbolic debug", "line number debug"};

char * translatesegmenttype(int16 type) {
    if (type < 8) return knowntypes[type];
    if (type < 0x0020) return "reserved";
    if (type < 0x1000) return "reserved - moscow";
    if (type < 0x8000) return "reserved - system dependant";
    if (type < 0xFFFF) return "reserved - other";
    if (type == 0xFFFF) return "invalid type code";
    return "type code out of range";
}    

int main(int argc,char **argv) {
  char id[7];
  long l;
  int16 s;
  int verbose = 0;
  long offset;
  int  foundnullsegment = 0;
  int  version;
  long segmentcontentlength = 0;
  int  nsegments = 0;
  long headerlength = 0;
  long objectlength = 0;

  puts("RDOFF Dump utility v2.1\n(c) Copyright 1996,99,2000 Julian R Hall, Yuri M Zaporogets");

  if (argc < 2) {
    fputs("Usage: rdfdump [-v] <filename>\n",stderr);
    exit(1);
  }

  if (! strcmp (argv[1], "-v") )
  {
    verbose = 1;
    if (argc < 3)
    {
      fputs("required parameter missing\n",stderr);
      exit(1);
    }
    argv++;
  }

  infile = fopen(argv[1],"rb");
  if (! infile) {
    fprintf(stderr,"rdfdump: Could not open %s\n",argv[1]);
    exit(1);
  }

  fread(id,6,1,infile);
  if (strncmp(id,"RDOFF",5)) {
    fputs("rdfdump: File does not contain valid RDOFF header\n",stderr);
    exit(1);
  }

  printf("File %s: RDOFF version %c\n\n",argv[1],id[5]);
  if (id[5] < '1' || id[5] > '2') {
    fprintf(stderr,"rdfdump: unknown RDOFF version '%c'\n",id[5]);
    exit(1);
  }
  version = id[5] - '0';

  if (version > 1) {
      fread(&l, 4, 1, infile);
      objectlength = translatelong(l);
      printf("Object content size: %ld bytes\n", objectlength);
  }

  fread(&l,4,1,infile);
  headerlength = translatelong(l);
  printf("Header (%ld bytes):\n",headerlength);
  print_header(headerlength, version);

  if (version == 1) {
      fread(&l,4,1,infile);
      l = translatelong(l);
      printf("\nText segment length = %ld bytes\n",l);
      offset = 0;
      while(l--) {
	  fread(id,1,1,infile);
	  if (verbose) {
	      if (offset % 16 == 0)
		  printf("\n%08lx ", offset);
	      printf(" %02x",(int) (unsigned char)id[0]);
	      offset++;
	  }
      }
      if (verbose) printf("\n\n");
      
      fread(&l,4,1,infile);
      l = translatelong(l);
      printf("Data segment length = %ld bytes\n",l);

      if (verbose)
      {
	  offset = 0;
	  while (l--) {
	      fread(id,1,1,infile);
	      if (offset % 16 == 0)
		  printf("\n%08lx ", offset);
	      printf(" %02x",(int) (unsigned char) id[0]);
	      offset++;
	  }
	  printf("\n");
      }
  }
  else
  {
      do {
	  fread(&s,2,1,infile);
	  s = translateshort(s);
	  if (!s) {
	      printf("\nNULL segment\n");
	      foundnullsegment = 1;
	      break;
	  }
	  printf("\nSegment:\n  Type   = %04X (%s)\n",(int)s,
		 translatesegmenttype(s));
	  nsegments++;

	  fread(&s,2,1,infile);
	  printf("  Number = %04X\n",(int)translateshort(s));
	  fread(&s,2,1,infile);
	  printf("  Resrvd = %04X\n",(int)translateshort(s));
	  fread(&l,4,1,infile);
	  l = translatelong(l);
	  printf("  Length = %ld bytes\n",l);
	  segmentcontentlength += l;

	  offset = 0;
	  while(l--) {
	      fread(id,1,1,infile);
	      if (verbose) {
		  if (offset % 16 == 0)
		      printf("\n%08lx ", offset);
		  printf(" %02x",(int) (unsigned char)id[0]);
		  offset++;
	      }
	  }
	  if (verbose) printf("\n");
      } while (!feof(infile));
      if (! foundnullsegment)
	  printf("\nWarning: unexpected end of file - "
		 "NULL segment not found\n");

      printf("\nTotal number of segments: %d\n", nsegments);
      printf("Total segment content length: %ld bytes\n",segmentcontentlength);

      /* calculate what the total object content length should have been */
      l = segmentcontentlength + 10 * (nsegments+1) + headerlength + 4;
      if (l != objectlength)
	  printf("Warning: actual object length (%ld) != "
		 "stored object length (%ld)\n", l, objectlength);
  }
  fclose(infile);
  return 0;
}
