/*
    exe2com 1.01 - an exe2bin replacement
          by Chris Dunford/The Cove Software Group.
    see exe2com.doc for more info.

    usage: exe2com file [file]
    usage is the same as exe2bin except:
        1. Output defaults to COM rather than BIN
        2. Binary fixup option not supported
        3. Checksum not verified
        4. Provides more useful error messages, and a warning if a
           COM file is being created with initial IP != 0x100.

    Compiler notes:
        The original executable of this file was produced with the
        Computer Innovations C86 compiler (v2.2).

    This program was knocked together in about an hour in response to
    the removal of EXE2BIN from the standard DOS distribution disks.
    Improvements/corrections are encouraged.

    Program donated to the public domain by the author.

    cjd 4/17/87

    Modified to run under Turbo C by Roger Schlafly, Borland.
    (It may not run on C86 any more.)
    Compile with
      tcc -Id:\c -Ld:\c -mt -w -O -Z -p exe2com
      exe2com exe2com
    or
      tcc -Id:\c -Ld:\c -M -mt -w -DDEBUG=1 -O -Z -p exe2com
      exe2com exe2com

    rss 6/26/87
*/

#include <stdio.h>

#include <string.h>
/* for unlink */
#include <dos.h>
/* for exit */
#include <stdlib.h>
/* for tolower */
#include <ctype.h>

/* Conversion error codes */
#define BADREAD  1
#define BADWRITE 2
#define BADSIG   3
#define HASRELO  4
#define HAS_SS   5
#define HAS_CS   6
#define BAD_IP   7
#define TOO_BIG  8

/*
**  Define structure of fixed-format part of EXE file header
*/
struct exe_header {
        char exe_sig[2];    /* EXE file signature: "MZ" */
    unsigned excess,        /* Image size mod 512 (valid bytes in last page) */
             pages,         /* # 512-byte pages in image */
             relo_ct,       /* Count of relocation table entries */
             hdr_size,      /* Size of header, in paragraphs */
             min_mem,       /* Min required memory */
             max_mem,       /* Max required memory */
             ss,            /* Stack seg offset in load module */
             sp,            /* Initial value of SP */
             cksum,         /* File checksum */
             ip,            /* Initial value of IP */
             cs,            /* CS offset in load module */
             relo_start,    /* Offset of first relo item */
             ovl_num;       /* Overlay number */
} xh;

FILE *fi,                   /* Input file stream */
     *fo;                   /* Output file stream */

char fin[129],              /* Input file name */
     fon[129];              /* Output file name */


unsigned long code_start,   /* Offset of program image in EXE file */
              code_size;    /* Size of program image, in bytes */


void err_xit (unsigned code);
void convert (void);
void read_hdr(void);
void init (unsigned argc, char *argv[]);

char *lower (char *cp)
{
char *cp0;
   for (cp0=cp; (*cp=tolower (*cp)) != 0; ++cp)
           ;
   return cp0;
}

void cdecl main(unsigned argc, char *argv[])
{
    init (argc, argv);
    read_hdr ();
    convert ();
}


/*
**  Initialize - get filenames and open/create files
*/
void init (unsigned argc, char *argv[])
{
char *cp;

    printf ("exe2com 1.01\n");

    /* Check arg count */
    if (argc < 2 || argc > 3) {
        fprintf (stderr, "usage: exe2com file [file]\n");
        exit (1);
    }

    /* If argv[1] (the input file) has no extension, add .EXE */
    strcpy (fin, lower (argv[1]));
    if (!strchr (fin, '.'))
        strcat (fin, ".exe");

    /* Get or construct output file name */
    if (argc == 3)
        strcpy (fon, lower (argv[2]));
    else
        strcpy (fon, fin);

    /* Check output extension--change EXE to COM, or add COM */
    if (!(cp = strchr (fon, '.')))
        strcat (fon, ".com");
    else if (strcmp (cp, ".exe") == 0)
        strcpy (cp, ".com");

#ifdef DEBUG
    printf ("input=%s, output=%s\n", fin, fon);
#endif

    /* Try to open input file */
    if (!(fi = fopen (fin, "rb"))) {
        fprintf (stderr, "exe2com: can't find input file %s\n", fin);
        exit (1);
    }

    /* Try to create output file */
    if (!(fo = fopen (fon, "wb"))) {
        fprintf (stderr, "exe2com: can't open output file %s\n", fin);
        exit (1);
    }
}


/*
**  Read and check the EXE file header
*/
void read_hdr(void)
{

    /* Read the formatted portion of the header */
    if (!fread (&xh, sizeof (struct exe_header), 1, fi))
        err_xit (BADREAD);

#ifdef DEBUG
    printf ("EXE file header:\n");
    printf ("  signature      %c%c\n", xh.exe_sig[0], xh.exe_sig[1]);
    printf ("  bytes last pg  %04x\n", xh.excess);
    printf ("  pages          %04x\n", xh.pages);
    printf ("  relo count     %04x\n", xh.relo_ct);
    printf ("  header size    %04x\n", xh.hdr_size);
    printf ("  min mem        %04x\n", xh.min_mem);
    printf ("  max mem        %04x\n", xh.max_mem);
    printf ("  ss             %04x\n", xh.ss);
    printf ("  sp             %04x\n", xh.sp);
    printf ("  checksum       %04x\n", xh.cksum);
    printf ("  ip             %04x\n", xh.ip);
    printf ("  cs             %04x\n", xh.cs);
    printf ("  relo tbl start %04x\n", xh.relo_start);
    printf ("  overlay nbr    %04x\n", xh.ovl_num);
#endif

    /* Check header; to be convertible, must have:
    **      -- first two bytes == "MZ"
    **      -- no relocatable items
    **      -- no stack segment
    **      -- no code segment
    **      -- IP == 0 or 100
    */
    if (strncmp (xh.exe_sig, "MZ", 2))
        err_xit (BADSIG);
    if (xh.relo_ct)
        err_xit (HASRELO);
    if (xh.ss || xh.sp)
        err_xit (HAS_SS);
    if (xh.ip != 0 && xh.ip != 0x100)
        err_xit (BAD_IP);

    /* Compute offset of program image in module, and program size.
    **
    ** The program size is computed as follows; it cannot exceed 64K bytes:
    **     512 * (# EXE pages - 1)
    **   + valid bytes in last EXE page
    **   - offset of program image in EXE file
    **
    ** Note that if the IP is nonzero, we will skip the first
    ** IP bytes of the program image, and copy IP bytes fewer
    ** than the actual size.
    */
    code_start = ((unsigned long) xh.hdr_size) << 4;
    code_size = (unsigned long) (xh.pages-1) * 512 + xh.excess - code_start;
    if (code_size >= 65536L)
        err_xit (TOO_BIG);

    /* Issue a warning if COM file and IP != 0x100 */
    if (!strcmp (strchr (fon, '.'), ".com") && xh.ip != 0x100)
        fprintf (stderr, "exe2com warning: COM file, initial IP not 100H\n");

}



/*
**  Convert the file.  Nothing to do, really, other than
**  reading the image (which follows the header), and
**  dumping it back out to disk.
*/
void convert (void)
{
char buffer[512];
unsigned wsize;

    /* Seek to start of program image, skipping IP bytes */
    if (fseek (fi, code_start+xh.ip, 0) != 0)
        err_xit (BADREAD);

    /* Reduce the "remaining" byte count by IP bytes */
    code_size -= xh.ip;

    /* Read blocks and copy to output */
    while (code_size) {

        /* Read block */
        if (!fread (buffer, 1, 512, fi))
            err_xit (BADREAD);

        /* Set count of bytes to write, write block */
        wsize = (unsigned) (code_size > 512 ? 512 : code_size);
        if (!fwrite (buffer, wsize, 1, fo))
            err_xit (BADWRITE);

        /* Subtract bytes written from remaining byte count */
        code_size -= wsize;
    }

    /* All done, close the two files */
    fclose (fi);
    fclose (fo);
}


/*
**  Display an error message, delete output file, exit.
*/
void err_xit (unsigned code)
{
char msg[64];

    switch (code) {
        case BADREAD:   strcpy (msg, "error reading EXE header");
                        break;
        case BADWRITE:  strcpy (msg, "error writing output file");
                        break;
        case BADSIG:    strcpy (msg, "invalid EXE file signature");
                        break;
        case HASRELO:   strcpy (msg, "EXE has relocatable items");
                        break;
        case HAS_SS:    strcpy (msg, "EXE has stack segment");
                        break;
        case HAS_CS:    strcpy (msg, "EXE has nonzero CS");
                        break;
        case BAD_IP:    strcpy (msg, "IP not 0 or 100H");
                        break;
        case TOO_BIG:   strcpy (msg, "program exceeds 64K");
                        break;
        default:        strcpy (msg, "unknown error");
    }

    fprintf (stderr, "exe2com: %s, can't convert\n", msg);

    /* Close two files and delete partial output */
    fclose (fi);
    fclose (fo);
    unlink (fon);

    /* Exit with errorlevel 1 */
    exit (1);
}