#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>

typedef struct _objRecord
{
   unsigned char type;
   short int length;
   unsigned char info[1024];
} objRecord;

void
pubdef (objRecord *r)
{

}

void
linnum (objRecord *r)
{

}

void
segdef (objRecord *r)
{
   /* Convert the 32 bit segdef record to 16 bit */
   unsigned char *readpos, *writepos;
   unsigned long seglength;

   readpos = writepos = &(r->info[0]);
   /* Modify the ACBP bit */
   if ((*readpos & 2) != 0) {
      fputs("4Gb segment length set to 64kb!", stderr);
   }
   *writepos &= 0xfe; /* Zero the P bit */
   if ((*readpos & 0xe0) == 0) {
      readpos += 4;
      writepos += 4;
   } else {
      readpos++;
      writepos++;
   }
   seglength = *((unsigned long *)readpos);
   if (seglength > 65535) {
      *((unsigned short *)writepos) = 0;
      r->info[0] |= 2;
      fprintf(stderr, "Segment of %ld bytes set to 64kb!", seglength);
   }
   readpos += 4;
   writepos += 2;
   *writepos++ = *readpos++;
   *writepos++ = *readpos++;
   *writepos++ = *readpos++;
   readpos = &(r->type);
   r->type = 0x98;
   r->length = writepos - &(r->info[0]) + 1;
   *writepos = 0;
   while (readpos < writepos) {
      *writepos -= *readpos++;
   }
}

void
fixupp (objRecord *r)
{
   /* Convert the 32 bit fixupp record to 16 bit */
   unsigned char *readpos, *writepos, *endpos;

   readpos = writepos = &(r->info[0]);
   endpos = readpos + r->length - 1;
   while (readpos < endpos) {
      if ((*readpos & 0x80) == 0) {
         /* thread subrecord */
         *writepos++ = *readpos++;
      } else {
         /* fixupp subrecord */
         unsigned char fixdata;

         *writepos++ = (*readpos++ & 0xc3) | 4; /* convert to 16 bit */
         *writepos++ = *readpos++; /* Data record offset */
         fixdata = *readpos;
         *writepos++ = *readpos++; /* Fix Data byte */
         if (fixdata & 0x80 == 0) {
            *writepos++ = *readpos++;
         }
         if (fixdata & 0x08 == 0) {
            *writepos++ = *readpos++;
         }
         if (fixdata & 0x04 == 0) {
            *writepos++ = *readpos++;
            *writepos++ = *readpos++;
            readpos += 2;
         }
      }
   }
   readpos = &(r->type);
   r->type = 0x9c;
   r->length = writepos - &(r->info[0]) + 1;
   *writepos = 0;
   while (readpos < writepos) {
      *writepos -= *readpos++;
   }
}

void
ledata (objRecord *r)
{
   /* Convert the 32 bit ledata record to 16 bit */
   unsigned char *readpos, *writepos, *endpos;

   readpos = writepos = &(r->info[0]);
   endpos = readpos + r->length - 1;
   readpos++;
   if (*((unsigned long *)readpos) > 65535UL) {
      fputs("Segment offset > 64kB.  Cannot convert", stderr);
      exit(255);
   }
   readpos += 4;
   writepos += 3;
   while (readpos < endpos) {
      *writepos++ = *readpos++;
   }
   readpos = &(r->type);
   r->type = 0xa0;
   r->length = writepos - &(r->info[0]) + 1;
   *writepos = 0;
   while (readpos < writepos) {
      *writepos -= *readpos++;
   }
}

void
lidata (objRecord *r)
{

}

int
ProcessRecords (FILE *file32, FILE *file16)
{
   objRecord record;
   unsigned long offset = 0, read_offset;

   record.type = 0;
   record.length = 0;

   while (fread(&record, 1, 3, file32) == 3 && record.length > 0 && record.length < 0x3ff) {
      fprintf(stderr, "%04lx %02x %04x", offset, record.type, record.length);
      read_offset = offset;
      offset += 3 + record.length;
      if (fread(&record.info, 1, record.length, file32) == record.length) {
         unsigned char checksum = record.type + (record.length & 0xff)+ ((record.length >> 8) & 0xff);
         short int i; 
         for (i = 0; i < record.length; i++) {
            checksum += record.info[i];
         }
         if (checksum == 0) {
            switch (record.type) {
/*
               case 0x91:
                  pubdef(&record);
                  break;

               case 0x95:
                  linnum(&record);
                  break;
*/
               case 0x99:
                  segdef(&record);
                  break;

               case 0x9d:
                  fixupp(&record);
                  break;

               case 0xa1:
                  ledata(&record);
                  break;
/*
               case 0xa3:
                  lidata(&record);
                  break;
*/
               default:
                  if ((record.type & 1) != 0) {
                     fprintf(stderr, "Unconverted 32 bit record at offset 0x%lx.  Type 0x%02x\n", read_offset, record.type);
                  }
                  break;
            }
            fwrite(&record, 1, record.length + 3, file16);
         }
         fprintf(stderr, " %02x", checksum);
      }
      fprintf(stderr, "\n");
      record.length = 0;
   }
   return record.length;
}

void
usage (void)
{
   fputs("32 bit to 16 bit OMF .OBJ file convertor\n\n", stderr);
   fputs("Usage: omf3216 [32bitobj]\n\n", stderr);
   fputs("Where: 32bitobj is the 32 bit .OBJ file to be converted\n", stderr);
   fputs("       If 32bitobj is not specified input comes from stdin\n\n", stderr);
   fputs("       The 16 bit .OBJ file is sent to stdout\n", stderr);
}

int
main (int argc, char *argv[])
{
   if (argc > 1) {
      FILE *infile;

      if (*argv[1] == '-' || *argv[1] == '/') {
         usage();
         return 255;
      }
      if ((infile = fopen(argv[1], "rb")) != NULL) {
         int retvalue;

         setmode(fileno(stdout), O_BINARY);
         retvalue = ProcessRecords(infile, stdout);
         fclose(infile);
         return retvalue;
      }
   } else {
      setmode(fileno(stdin), O_BINARY);
      setmode(fileno(stdout), O_BINARY);
      return ProcessRecords(stdin, stdout);
   }
   return 255;
}
