/*
 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to sim@ibm1130.org
 */

// ---------------------------------------------------------------------------------
// MKBOOT - reads card loader format cards and produces an absolute core image that
// can then be dumped out in 1130 IPL, 1800 IPL or Core Image loader formats.
//
// Usage: mkboot [-v] binfile outfile [1130|1800|core [loaddr [hiaddr [ident]]]]"
//
// Arguments:
//			binfile - name of assembler output file (card loader format, absolute output)
//			outfile - name of output file to create
//			mode	- output mode, default is 1130 IPL format
//			loaddr  - low address to dump. Default is lowest address loaded from binfile
//			hiaddr  - high address to dump. Defult is highest address loaded from binfile
//			ident	- ident string to write in last 8 columns. Omit when when writing an
//					  1130 IPL card that requires all 80 columns of data.
//
// Examples:
//		mkboot somefile.bin somefile.ipl 1130
//
//			loads somefile.bin, writes object in 1130 IPL format to somefile.ipl
//			Up to 80 columns will be written depending on what the object actually uses
//
//		mkboot somefile.bin somefile.ipl 1130 /0 /47 SOMEF
//
//			loads somefile.bin. Writes 72 columns (hex 0 to hex 47), with ident columns 73-80 = SOMEF001
//
//		mkboot somefile.bin somefile.dat core 0 0 SOMEF001
//
//			loads somefile.bin and writes a core image format deck with ident SOMEF001, SOMEF002, etc
//
//		For other examples of usage, see MKDMS.BAT
//
//         1.00 - 2002Apr18 - first release. Tested only under Win32. The core image
//							  loader format is almost certainly wrong. Cannot handle
//							  relocatable input decks, but it works well enough to
//							  load DSYSLDR1 which is what we are after here.
// ---------------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "util_io.h"

#ifndef TRUE
    #define BOOL  int
    #define TRUE  1
    #define FALSE 0
#endif

#ifndef _WIN32
    int strnicmp (char *a, char *b, int n);
    int strcmpi (char *a, char *b);
#endif

#define BETWEEN(v,a,b) (((v) >= (a)) && ((v) <= (b)))
#define MIN(a,b)       (((a) <= (b)) ? (a) : (b))
#define MAX(a,b)       (((a) >= (b)) ? (a) : (b))

#define MAXADDR 4096

typedef enum {R_ABSOLUTE = 0, R_RELATIVE = 1, R_LIBF = 2, R_CALL = 3} RELOC;

typedef enum {B_1130, B_1800, B_CORE} BOOTMODE;

BOOL verbose = FALSE;
char *infile = NULL, *outfile = NULL;
BOOTMODE mode = B_1130;
int addr_from = 0, addr_to = 79;
int  outcols = 0;								// columns written in using card output
int maxiplcols = 80;
char cardid[9];									// characters used for IPL card ID
int pta = 0;
int load_low = 0x7FFFFFF;
int load_high = 0;
unsigned short mem[MAXADDR];			// small core!

// mkboot - load a binary object deck into core and dump requested bytes as a boot card

void bail (char *msg);
void verify_checksum(unsigned short *card);
char *upcase (char *str);
void unpack (unsigned short *card, unsigned short *buf);
void dump (char *fname);
void loaddata (char *fname);
void write_1130 (void);
void write_1800 (void);
void write_core (void);
void flushcard(void);
int  ascii_to_hollerith (int ch);
void corecard_init (void);
void corecard_writecard (char *sbrk_text);
void corecard_writedata (void);
void corecard_flush (void);
void corecard_setorg (int neworg);
void corecard_writew (int word, RELOC relative);
void corecard_endcard (void);

char *fname = NULL;
FILE *fout;

int main (int argc, char **argv)
{
	char *arg;
	static char usestr[] = "Usage: mkboot [-v] binfile outfile [1130|1800|core [loaddr [hiaddr [ident]]]]";
	int i, ano = 0, ok;

	for (i = 1; i < argc; i++) {
		arg = argv[i];
		if (*arg == '-') {
			arg++;
			while (*arg) {
				switch (*arg++) {
					case 'v':
						verbose = TRUE;
						break;
					default:
						bail(usestr);
				}
			}
		}
		else {
			switch (ano++) {
				case 0:
					infile = arg;
					break;

				case 1:
					outfile = arg;
					break;

				case 2:
					if       (strcmp(arg, "1130")  == 0) mode = B_1130;
					else if  (strcmp(arg, "1800")  == 0) mode = B_1800;
					else if  (strcmpi(arg, "core") == 0) mode = B_CORE;
					else bail(usestr);
					break;

				case 3:
					if (strnicmp(arg, "0x", 2) == 0) ok = sscanf(arg+2, "%x", &addr_from);
					else if (arg[0] == '/')          ok = sscanf(arg+1, "%x", &addr_from);
					else                             ok = sscanf(arg,   "%d", &addr_from);
					if (ok != 1) bail(usestr);
					break;

				case 4:
					if (strnicmp(arg, "0x", 2) == 0) ok = sscanf(arg+2, "%x", &addr_to);
					else if (arg[0] == '/')          ok = sscanf(arg+1, "%x", &addr_to);
					else                             ok = sscanf(arg,   "%d", &addr_to);
					if (ok != 1) bail(usestr);
					break;

				case 5:
					strncpy(cardid, arg, 9);
					cardid[8] = '\0';
					upcase(cardid);
					break;

				default:
					bail(usestr);
			}
		}
	}

	if (*cardid == '\0')
		maxiplcols = (mode == B_1130) ? 80 : 72;
	else {
		while (strlen(cardid) < 8)
			strcat(cardid, "0");
		maxiplcols = 72;
	}

	loaddata(infile);

	if (mode == B_1800)
		write_1800();
	else if (mode == B_CORE)
		write_core();
	else
		write_1130();

	return 0;
}

void write_1130 (void)
{
	int addr;
	unsigned short word;

	if ((fout = fopen(outfile, "wb")) == NULL) {
		perror(outfile);
		exit(1);
	}

	for (addr = addr_from; addr <= addr_to; addr++) {
		if (outcols >= maxiplcols)
			flushcard();

		word = mem[addr];

		// if F or L bits are set, or if high 2 bits of displacement are unequal, it's bad
		if ((word & 0x0700) || ! (((word & 0x00C0) == 0) || ((word & 0x00C0) == 0x00C0)))
			printf("Warning: word %04x @ %04x may not IPL properly\n", word & 0xFFFF, addr);

		word = ((word & 0xF800) >> 4) | (word & 0x7F);	// convert to 1130 IPL format

		putc((word & 0x000F) << 4,  fout);				// write the 12 bits in little-endian binary AABBCC00 as CC00 AABB
		putc((word & 0x0FF0) >> 4, fout);
		outcols++;
	}
	flushcard();
	fclose(fout);
}

void write_1800 (void)
{
	int addr;
	unsigned short word;

	if ((fout = fopen(outfile, "wb")) == NULL) {
		perror(outfile);
		exit(1);
	}

	for (addr = addr_from; addr <= addr_to; addr++) {
		word = mem[addr];

		if (outcols >= maxiplcols)
			flushcard();

		putc(0, fout);
		putc(word & 0xFF, fout);		// write the low 8 bits in little-endian binary
		outcols++;

		putc(0, fout);
		putc((word >> 8) & 0xFF, fout);		// write the high 8 bits in little-endian binary
		outcols++;
	}
	flushcard();
	fclose(fout);
}

void write_core (void)
{
	int addr;

	if ((fout = fopen(outfile, "wb")) == NULL) {
		perror(outfile);
		exit(1);
	}

	addr_from = load_low;
	addr_to   = load_high;

	maxiplcols = 72;
	corecard_init();
	corecard_setorg(addr_from);

	for (addr = addr_from; addr <= addr_to; addr++) {
		corecard_writew(mem[addr], 0);
	}

	corecard_flush();
	corecard_endcard();
	fclose(fout);
}

void flushcard (void)
{
	int i, hol, ndig;
	char fmt[20], newdig[20];

	if (outcols <= 0)
		return;							// nothing to flush

	while (outcols < maxiplcols) {		// pad to required number of columns with blanks (no punches)
		putc(0, fout);
		putc(0, fout);
		outcols++;
	}

	if (*cardid) {						// add label
		for (i = 0; i < 8; i++) {		// write label as specified
			hol = ascii_to_hollerith(cardid[i] & 0x7F);
			putc(hol & 0xFF, fout);
			putc((hol >> 8) & 0xFF, fout);
		}

		ndig = 0;						// count trailing digits in the label
		for (i = 8; --i >= 0; ndig++)
			if (! isdigit(cardid[i]))
				break;

		i++;							// index of first digit in trailing sequence

		if (ndig > 0) {						// if any, increment them
			sprintf(fmt, "%%0%dd", ndig);	// make, e.g. %03d
			sprintf(newdig, fmt, atoi(cardid+i)+1);
			newdig[ndig] = '\0';			// clip if necessary
			strcpy(cardid+i, newdig);		// replace for next card's sequence number
		}
	}

	outcols = 0;
}

void show_data (unsigned short *buf)
{
	int i, n, jrel, rflag, nout, ch, reloc;

	n = buf[2] & 0x00FF;

	printf("%04x: ", buf[0]);

	jrel = 3;
	nout = 0;
	rflag = buf[jrel++];
	for (i = 0; i < n; i++) {
		if (nout >= 8) {
			rflag = buf[jrel++];
			putchar('\n');
			printf("      ");
			nout = 0;
		}
		reloc = (rflag >> 14) & 0x03;
		ch = (reloc == R_ABSOLUTE) ? ' ' :
			 (reloc == R_RELATIVE) ? 'R' :
			 (reloc == R_LIBF)     ? 'L' : '@';

		printf("%04x%c ", buf[9+i], ch);
		rflag <<= 2;
		nout++;
	}
	putchar('\n');
}

void loadcard (unsigned short *buf)
{
	int addr, n, i;
	
	addr = buf[0];
	n = buf[2] & 0x00FF;

	for (i = 0; i < n; i++) {
		if (addr >= MAXADDR)
			bail("Program doesn't fit into 4K");
		mem[addr] = buf[9+i];

		load_low  = MIN(addr, load_low);
		load_high = MAX(addr, load_high);
		addr++;
	}
}

void loaddata (char *fname)
{
	FILE *fp;
	BOOL first = TRUE;
	unsigned short card[80], buf[54], cardtype;

	if ((fp = fopen(fname, "rb")) == NULL) {
		perror(fname);
		exit(1);
	}

	if (verbose)	
		printf("\n%s:\n", fname);

	while (fxread(card, sizeof(card[0]), 80, fp) > 0) {
		unpack(card, buf);
		verify_checksum(card);

		cardtype = (buf[2] >> 8) & 0xFF;

		if (cardtype == 1 && ! first) {			// sector break
			if (verbose)
				printf("*SBRK\n");
			continue;
		}
		else {
			switch (cardtype) {
				case 0x01:
					if (verbose)
						printf("*ABS\n");
					break;
				case 0x02:
				case 0x03:
				case 0x04:
				case 0x05:
				case 0x06:
				case 0x07:
					bail("Data must be in absolute format");
					break;

				case 0x0F:
					pta = buf[3];			// save program transfer address
					if (verbose)
						printf("*END\n");
					break;

				case 0x0A:
					if (verbose)
						show_data(buf);
					loadcard(buf);
					break;
				default:
					bail("Unexpected card type");
			}
		}
		first = FALSE;
	}

	fclose(fp);
}

void bail (char *msg)
{
	fprintf(stderr, "%s\n", msg);
	exit(1);
}

void unpack (unsigned short *card, unsigned short *buf)
{
	int i, j;
	unsigned short wd1, wd2, wd3, wd4;

	for (i = j = 0; i < 54; i += 3, j += 4) {
		wd1 = card[j];
		wd2 = card[j+1];
		wd3 = card[j+2];
		wd4 = card[j+3];

		buf[i  ] = (wd1        & 0xFFF0) | ((wd2 >> 12) & 0x000F);
		buf[i+1] = ((wd2 << 4) & 0xFF00) | ((wd3 >>  8) & 0x00FF);
		buf[i+2] = ((wd3 << 8) & 0xF000) | ((wd4 >>  4) & 0x0FFF);
	}
}

void verify_checksum (unsigned short *card)
{
//	unsigned short sum;

	if (card[1] == 0)			// no checksum
		return;

//	if (sum != card[1])
//		printf("Checksum %04x doesn't match card %04x\n", sum, card[1]);
}

typedef struct {
	int		hollerith;
	char	ascii;
} CPCODE;

static CPCODE cardcode_029[] =
{
	0x0000,		' ',
	0x8000, 	'&',	 		// + in 026 Fortran
	0x4000,		'-',
	0x2000,		'0',
	0x1000,		'1',
	0x0800,		'2',
	0x0400,		'3',
	0x0200,		'4',
	0x0100,		'5',
	0x0080,		'6',
	0x0040,		'7',
	0x0020,		'8',
	0x0010,		'9',
	0x9000, 	'A',
	0x8800,		'B',
	0x8400,		'C',
	0x8200,		'D',
	0x8100,		'E',
	0x8080,		'F',
	0x8040,		'G',
	0x8020,		'H',
	0x8010,		'I',
	0x5000, 	'J',
	0x4800,		'K',
	0x4400,		'L',
	0x4200,		'M',
	0x4100,		'N',
	0x4080,		'O',
	0x4040,		'P',
	0x4020,		'Q',
	0x4010,		'R',
	0x3000, 	'/',
	0x2800,		'S',
	0x2400,		'T',
	0x2200,		'U',
	0x2100,		'V',
	0x2080,		'W',
	0x2040,		'X',
	0x2020,		'Y',
	0x2010,		'Z',
	0x0820,		':',
	0x0420,		'#',		// = in 026 Fortran
	0x0220,		'@',		// ' in 026 Fortran
	0x0120,		'\'',
	0x00A0,		'=',
	0x0060,		'"',
	0x8820,		'c',		// cent
	0x8420,		'.',
	0x8220,		'<',		// ) in 026 Fortran
	0x8120,		'(',
	0x80A0,		'+',
	0x8060,		'|',
	0x4820,		'!',
	0x4420,		'$',
	0x4220,		'*',
	0x4120,		')',
	0x40A0,		';',
	0x4060,		'n',		// not
	0x2820,		'x',		// what?
	0x2420,		',',
	0x2220,		'%',		// ( in 026 Fortran
	0x2120,		'_',
	0x20A0,		'>',
	0x2060,		'>',
};

int ascii_to_hollerith (int ch)
{
	int i;

	for (i = 0; i < sizeof(cardcode_029) / sizeof(CPCODE); i++)
		if (cardcode_029[i].ascii == ch)
			return cardcode_029[i].hollerith;

	return 0;
}

// ---------------------------------------------------------------------------------
// corecard - routines to write IBM 1130 Card object format
// ---------------------------------------------------------------------------------

unsigned short corecard[54];	// the 54 data words that can fit on a binary format card
int corecard_n   = 0;			// number of object words stored in corecard (0-45)
int corecard_seq = 1;			// card output sequence number
int corecard_org = 0;			// origin of current card-full
int corecard_maxaddr = 0;
BOOL corecard_first = TRUE;		// TRUE when we're to write the program type card

// corecard_init - prepare a new object data output card

void corecard_init (void)
{
	memset(corecard, 0, sizeof(corecard));		// clear card data
	corecard_n = 0;								// no data
	corecard[0] = corecard_org;					// store load address
	corecard_maxaddr = MAX(corecard_maxaddr, corecard_org-1);	// save highest address written-to (this may be a BSS)
}

// binard_writecard - emit a card. sbrk_text = NULL for normal data cards, points to comment text for sbrk card

void corecard_writecard (char *sbrk_text)
{
	unsigned short binout[80];
	int i, j;

	for (i = j = 0; i < 54; i += 3, j += 4) {
		binout[j  ] = ( corecard[i]          & 0xFFF0);
		binout[j+1] = ((corecard[i]   << 12) & 0xF000)  | ((corecard[i+1] >> 4) & 0x0FF0);
		binout[j+2] = ((corecard[i+1] <<  8) & 0xFF00)  | ((corecard[i+2] >> 8) & 0x00F0);
		binout[j+3] = ((corecard[i+2] <<  4) & 0xFFF0);
	}

	for (i = 0; i < 72; i++) {
		putc(binout[i] & 0xFF, fout);
		putc((binout[i] >> 8) & 0xFF, fout);
	}

	outcols = 72;								// add the ident
	flushcard();
}

// binard_writedata - emit an object data card

void corecard_writedata (void)
{
	corecard[1] = 0;							// checksum
	corecard[2] = 0x0000 | corecard_n;			// data card type + word count
	corecard_writecard(FALSE);					// emit the card
}

// corecard_flush - flush any pending binary data

void corecard_flush (void)
{
	if (corecard_n > 0)
		corecard_writedata();

	corecard_init();
}

// corecard_setorg - set the origin

void corecard_setorg (int neworg)
{
	corecard_org = neworg;			// set origin for next card
	corecard_flush();				// flush any current data & store origin
}

// corecard_writew - write a word to the current output card.

void corecard_writew (int word, RELOC relative)
{
	if (corecard_n >= 50)			// flush full card buffer (must be even)
		corecard_flush();

	corecard[3+corecard_n++] = word;
	corecard_org++;
}

// corecard_endcard - write end of program card

void corecard_endcard (void)
{
	corecard_flush();

	corecard[0] = 0;					// effective length: add 1 to max origin, then 1 more to round up
	corecard[1] = 0;
	corecard[2] = 0x8000;				// they look for negative bit but all else must be zero
	corecard[52] = 0xabcd;				// index register 3 value, this is for fun
	corecard[53] = pta;					// hmmm

	corecard_writecard(NULL);
}

/* ------------------------------------------------------------------------ 
 * upcase - force a string to uppercase (ASCII)
 * ------------------------------------------------------------------------ */

char *upcase (char *str)
{
	char *s;

	for (s = str; *s; s++) {
		if (*s >= 'a' && *s <= 'z')
			*s -= 32;
	} 

	return str;
}

#ifndef _WIN32

int strnicmp (char *a, char *b, int n)
{
	int ca, cb;

	for (;;) {
		if (--n < 0)					// still equal after n characters? quit now
			return 0;

		if ((ca = *a) == 0)				// get character, stop on null terminator
			return *b ? -1 : 0;

		if (ca >= 'a' && ca <= 'z')		// fold lowercase to uppercase
			ca -= 32;

		cb = *b;
		if (cb >= 'a' && cb <= 'z')
			cb -= 32;

		if ((ca -= cb) != 0)			// if different, return comparison
			return ca;

		a++, b++;
	}
}

int strcmpi (char *a, char *b)
{
	int ca, cb;

	for (;;) {
		if ((ca = *a) == 0)				// get character, stop on null terminator
			return *b ? -1 : 0;

		if (ca >= 'a' && ca <= 'z')		// fold lowercase to uppercase
			ca -= 32;

		cb = *b;
		if (cb >= 'a' && cb <= 'z')
			cb -= 32;

		if ((ca -= cb) != 0)			// if different, return comparison
			return ca;

		a++, b++;
	}
}

#endif
