/*
 * Copyright (C) 2004-2005
 * "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/*
 * January 29, 2005 - v0.5
 *
 * Tool exended to support original openwrt trx tool functionality.
 * OMG ... this code needs a rethink .. but not tonight josephine ..
 *
 */

/*
 * January 26, 2005 - v0.4
 *
 * Tool exended to support extraction of kernel, file system and
 * nvram vars from image.  This version was never published.
 *
 */

/*
 * October 10, 2004 - v0.3
 *
 * This "belky" tool is a hacked addition to OpenWRT's "trx" tool
 * (as at July 29, 2004) which in itself is a hacked replacement for
 * the 'trx' utility used to create wrt54g .trx firmware files. It
 * isn't pretty, but ... you know ...
 *
 * -- Adapted from "trx" by Manuel Novoa III of August 2004
 *
 */

/*
 * Nb: what was never made clear was that this file had been written
 *     to compile cleanly under Visual Studio - to build a native
 *     win32 binary.  Fortunately, this was not ever needed to be
 *     used, however, the hooks have been left in place for someone
 *     more adventurous.
 *
 */

/*
 * Compile;
 *
 *	touch stdafx.h
 *	cc belky.c -o belky
 *	# or; make belky
 *
 */


/* linux makefile should touch stdafx.h .. Visual Studio blows */
#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <endian.h>
#include <byteswap.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define  O_BINARY 0

#if __BYTE_ORDER == __BIG_ENDIAN
#define STORE32_LE(X)	bswap_32(X)
#define PULL32_LE(X)	bswap_32(X)
#elif __BYTE_ORDER == __LITTLE_ENDIAN
#define STORE32_LE(X)	(X)
#define PULL32_LE(X)	(X)
#else
#error unkown endianness!
#endif

uint32_t crc32buf(char *buf, size_t len);


/**********************************************************************/

/* Program Info */
char vernam[32] = "belky";
char vernum[6]  = "0.5";

/* Type codes */
int TYPE_TRX = 1;
int TYPE_EXTENDED = 2;

/* home-made goodies .. */
#define BELKY_MAGIC_H		0x44414F4C		/* "LOAD" */
#define TRX_MAGIC		0x30524448		/* "HDR0" */
#define BELKY_MAGIC_F		0x5241564E		/* "NVAR" */

// Seems the Header Version is wrong .. hmmm
#define BELKY_VERS_H		0x00000800		/* Version Magic */
#define TRX_VERSION		1
#define BELKY_VERS_F		0x00008000		/* Version Magic */
#define BELKY_MAX_LEN		0x3A0000


struct belky_header {
	uint32_t magic;	    /* 0 ... 3 "LOAD" */
	uint32_t len;	    /* 4 ... 7 Length of file including header */
	uint32_t crc32;	    /* 8 ... 11 32-bit CRC (ver to eof), LE, !bit */
	uint32_t version;   /* 12 ... 27 0x8000 (32bit LE), 0, 0, 0 */
	uint32_t pad_0;
	uint32_t pad_1;
	uint32_t pad_2;
};

struct belky_header *header;


struct trx_header {
	uint32_t magic;     /* "HDR0" */
	uint32_t len;       /* Length of file including header */
	uint32_t crc32;     /* 32-bit CRC from flag_version to end of file */
	uint32_t flag_version;  /* 0:15 flags, 16:31 version */
	uint32_t offsets[3];    /* Partition offsets from start of header */
};

struct trx_header *theader;


struct belky_footer {
	uint32_t magic;     /* 0 ... 3 "NVAR" */
	uint32_t len;       /* 4 ... 7 Length of footer including magic */
	uint32_t crc32;     /* 8 ... 11 32-bit CRC (ver to eof), LE */
	uint32_t version;   /* 12 ... 27 0x8000 (32bit LE), 0, 0, 0 */
	uint32_t pad_0;
	uint32_t pad_1;
	uint32_t pad_2;
};

struct belky_footer *footer;


/* Transient program global variables */
uint32_t action_type;
uint32_t fs_count;
uint32_t config_create;
uint32_t config_extract;
uint32_t config_maxlen;
char config_kernel[256];
char config_fs0[256];
char config_fs1[256];
char config_fs2[256];
char config_trx[256];
char config_output[256];
char config_userconf[256];

/* disabling this allows assumptions to be made, RE user request */
int allow_dumb_defaults = 0;

/**********************************************************************/

int
input_validation(void) {
	/* action (create/extractg) check */
	if (config_extract == 0 && config_create == 0) {
		printf("%s: no action (create or extract) specified "
		       "(try --help).\n", vernam);
		return -1;
	}
	if (config_extract > 0 && config_create > 0) {
		printf("%s: two actions (create and extract) specified "
		       "(try --help).\n", vernam);
		return -1;
	}

	/* output file check */
	if (strlen(config_output) == 0 && action_type == TYPE_EXTENDED) {
		if (allow_dumb_defaults) {
			strncpy(config_output, "firmware.belkin", 255);
		} else {
			printf("%s: no output file specified "
			       "(try --help).\n", vernam);
			return -1;
		}
	}

	/* OpenWRT firmware file check */
	if (strlen(config_trx) == 0) {
		if (allow_dumb_defaults) {
			strncpy(config_trx, "linux.trx", 255);
		}
		if (strlen(config_kernel) == 0 || strlen(config_fs0) ==0) {
			printf("%s: no trx, and no alternative (kernel or "
			       "filesystem) files specified (try --help).\n",
			       vernam);
			return -1;
		}
	}

	/* user.conf file check */
	if (strlen(config_userconf) == 0 && action_type == TYPE_EXTENDED) {
		if (allow_dumb_defaults) {
			strncpy(config_userconf, "user.conf", 255);
		}
		if (strlen(config_trx) == 0) {
			printf("%s: no trx or user.conf file specified "
			       "(try --help).\n", vernam);
			return -1;
		}
	}

	/* filesystem stacking check */
	if (strlen(config_fs1) != 0 && strlen(config_fs0) == 0) {
		printf("%s: fs1 specified without fs0 (?) "
		       "(try --help).\n", vernam);
		return -1;
	}
	/*
	if (strlen(config_fs2) != 0 && strlen(config_fs0) == 0) {
		printf("%s: fs2 specified without fs0 (?) "
		       "(try --help).\n", vernam);
		return -1;
	}
	if (strlen(config_fs2) != 0 && strlen(config_fs1) == 0) {
		printf("%s: fs2 specified without fs1 (?) "
		       "(try --help).\n", vernam);
		return -1;
	}
	*/
	if (strlen(config_fs0) != 0)
		fs_count++;
	if (strlen(config_fs1) != 0)
		fs_count++;
	/*
	if (strlen(config_fs2) != 0)
		fs_count++;
	*/

	/* action and pre-reqs check */
	if (action_type == TYPE_TRX &&
		(strlen(config_kernel) == 0 || fs_count == 0)) {
		printf("%s: TRX requested, but kernel or file system "
		       "parameter is missing (try --help).\n", vernam);
		return -1;
	}


	/* maxlen check */
	if (config_maxlen > BELKY_MAX_LEN) {
		printf("%s: *warning* maxlen [%u] exceeds default maximum "
		       " [%u]\n", vernam, config_maxlen, BELKY_MAX_LEN);
		printf("%s: Be very careful, you may overwrite NVRAM\n",
			vernam);
	} else {
		config_maxlen = BELKY_MAX_LEN;
	}


	return 0;
}


int
read_in_data_file(char * data_file, void * buffer, int maxbytes, int type) {
	int fd;
	int bytes;
	char filesize[3];

	bytes = 0;
	if (maxbytes < 0) {
		printf("%s: spare buffer space is negative -- "
		       "increase maxlen (?)\n", vernam);
		return -1;
	}

	if ((fd = open(data_file, O_BINARY | O_RDONLY )) < 0) {
		printf("%s: unable to open file [%s] for reading.\n",
			vernam, data_file);
		return -1;
	}

	if(lseek(fd, 0, SEEK_SET) < 0) {
		printf("%s: unable to seek to start of file [%s]\n",
			vernam, data_file);
		return -1;
	}

	/* special work for user.conf file */
	if (type == 2) {
		memset(filesize, 0, 3);
		bytes = read(fd, filesize, 2);
		if (bytes < 0) {
			printf("%s: unable to read from user.conf file [%s].\n",
				vernam, data_file);
			return -1;
		}
		bytes = (uint8_t)filesize[1] * 256 + (uint8_t)filesize[0];
		if (bytes > 0) {
			if ((bytes - 18) <= maxbytes) {
				maxbytes = bytes - 18;
			} else {
				printf("%s: more data in file [%s] than space "
				       "left in buffer -- increase maxlen?\n",
					vernam, data_file);
				return -1;
			}
		} else {
			printf("%s: unable to read file size from header of "
			       "file [%s].\n", vernam, data_file);
			return -1;
		}
	}

	bytes = read(fd, buffer, maxbytes);
	close(fd);
	if(bytes < 0) {
		printf("%s: unable to read from file [%s].\n",
			vernam, data_file);
		return -1;
	}

	return bytes;
}


int
write_out_data_file(char * data_file, void * buffer, int maxbytes, int type) {
	int fd;
	int bytes;
	char filesize[3];


	if (maxbytes <= 0) {
		printf("%s: embedded file to be written had corrupt "
		       "length (?)\n", vernam);
		return -1;
	}

	if ((fd = open(data_file, O_BINARY | O_RDWR | O_CREAT | O_TRUNC,
			S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )) < 0) {
		printf("%s: unable to open file [%s] for writing.\n",
			vernam, data_file);
		return -1;
	}

	bytes = write(fd, buffer, maxbytes);
	close(fd);
	if(bytes <= 0) {
		printf("%s: unable to write to file [%s].\n",
			vernam, data_file);
		return -1;
	}

	return bytes;
}


int
write_firmware(char * data_file, char * buffer, int maxbytes) {
	int fd;

	if ((fd = open(data_file, O_BINARY | O_RDWR | O_CREAT | O_TRUNC,
			S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )) < 0) {
		printf("%s: unable to open firmware file [%s] for writing.\n",
			vernam, data_file);
		return -1;
	}

	if (write(fd, buffer, maxbytes) <= 0) {
		printf("%s: unable to write to firmware file [%s].\n",
			vernam, data_file);
		return -1;
	}

	close(fd);

	return 0;
}


int
fill_headers(uint32_t filelen, uint32_t trxlen, uint32_t footerlen, int ft) {

	if (ft == 0) {
        	theader->magic	= STORE32_LE(TRX_MAGIC);
        	theader->flag_version = STORE32_LE((TRX_VERSION << 16));
        	theader->crc32	= crc32buf((char *) &theader->flag_version,
				  trxlen -
				  offsetof(struct trx_header, flag_version));
		theader->crc32	= STORE32_LE(theader->crc32);
		theader->len	= STORE32_LE(trxlen);
	}

	if (action_type == TYPE_TRX)
		return 0;

	footer->magic	= STORE32_LE(BELKY_MAGIC_F);
	footer->version	= STORE32_LE(BELKY_VERS_F);
	footer->crc32	= crc32buf((char *) &footer->version,
			  footerlen - offsetof(struct belky_footer, version));
	footer->crc32	= STORE32_LE(footer->crc32);
	footer->len	= STORE32_LE(footerlen);

	// Seems the Header Version is wrong .. hmmm
	header->magic	= STORE32_LE(BELKY_MAGIC_H);
	header->version = STORE32_LE(BELKY_VERS_F);
	header->crc32	= crc32buf((char *) &header->version,
			  filelen - offsetof(struct belky_header, version));
	header->crc32	= STORE32_LE(header->crc32);
	header->len	= STORE32_LE(filelen);

	return 0;
}


int
write_debug(char * data_file, char * buffer, int maxbytes) {
	int fd;

	if ((fd = open(data_file, O_BINARY | O_RDWR | O_CREAT | O_TRUNC ))
		< 0) {
		printf("%s: unable to open debug file [%s] for writing.\n",
			vernam, data_file);
		return -1;
	}

	if (write(fd, buffer, maxbytes) <= 0) {
		printf("%s: unable to write to debug file [%s].\n",
			vernam, data_file);
		return -1;
	}

	close(fd);

	return 0;
}


int
usage(int errlvl, char * runbin) {
	/* how many printf's must a man print out, before he is truly a man  */

	/*
	printf("%s: Usage;\n\n", vernam);
	printf(" Create;\n");
	printf(" %s -c[et] [-t {linux.trx}] [-u {user.conf}] "
		"[-e {firmware.belkin}] [-m maxlen]\n", runbin);
	printf("\n");
	printf(" Extract;\n");
	printf(" %s -x[et] [-t {linux.trx}] [-u {user.conf}] "
		"[-e {firmware.belkin}] [-m maxlen]\n", runbin);
	printf("\n\n");
	*/
	printf("%s: Parameters;\n\n", vernam);
	printf(" Long          Short   Args   Description\n");
	printf(" ~~~~~~~~~~    ~~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
		"~~~~~~~~~~~~~~~~\n");
	printf(" --create[et]  -ce|-ct        Create [e]xtended or [t]rx "
		"type firmware\n");
	printf(" --exfirm      -e      {file} Use extended firmware file "
		"{file}\n");
	printf(" --filesystem0 -fs0    {file} Use file {file} as the first "
		"file system\n");
	printf(" --filesystem1 -fs1    {file} Use file {file} as the second "
		"file system\n");
	/* 
	printf(" --filesystem2 -fs2    {file} Use file {file} as the third "
		"file system\n");
	*/
	printf(" --help        -h             Display this help screen\n");
	printf(" --kernel      -k      {file} Use kernel file {file}\n");
	printf(" --maxlen      -m      {num}  Over-ride / set firmware max "
		"length to {num}\n");
	printf(" --trx         -t      {file} Use OpenWRT (Broadcom) TRX "
		"firmware file {file}\n");
	printf(" --userconf    -u      {file} Use Belkin \"user.conf\" file "
		"{file}\n");
	printf(" --version     -V             Display version and exit\n");
	printf(" --extract[et] -xe|-xt        Extract [e]xtended or [t]rx "
		"type firmware\n");
	printf("\n\n");

	printf("%s: Example;\n\n", vernam);
	printf("  %s -xe -e F5D7230-4_x.bin "
		"-k kernel.bin -fs0 cramfs.bin -u nvram.txt\n", runbin);
	printf("  %s -ct -t linux.trx -k kernel.bin -fs0 cramfs.bin\n", runbin);
	printf("\n");

	/* the answer, my friend, is exit number one ... */
	/* the answer is exit number one .. */
	exit(errlvl);
}


void
parse_args(int argc, char** argv) {
	int argn;

	/* defaults */
	action_type		= 0;
	fs_count		= 0;
	config_create		= 0;
	config_extract		= 0;
	config_maxlen		= 0;
	memset(config_fs0,	0, 256);
	memset(config_fs1,	0, 256);
	memset(config_fs2,	0, 256);
	memset(config_kernel,	0, 256);
	memset(config_trx,	0, 256);
	memset(config_output,	0, 256);
	memset(config_userconf, 0, 256);

	argn = 1;
	while (argn < argc && argv[argn][0] == '-') {
		// -ce, --createe
		if (strcmp(argv[argn], "-ce") == 0 ||
			  strcmp(argv[argn], "--createe") == 0) {
			config_create = TYPE_EXTENDED;
			action_type = config_create;
		}
		// -ct, --createt
		else if (strcmp(argv[argn], "-ct") == 0 ||
			  strcmp(argv[argn], "--createt") == 0) {
			config_create = TYPE_TRX;
			action_type = config_create;
		}
		// -e, --exfirm
		else if ((strcmp(argv[argn], "-e") == 0 ||
			  strcmp(argv[argn], "--exfirm") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_output, argv[argn], 255);
		}
		// -fs0, --filesystem0
		else if ((strcmp(argv[argn], "-fs0") == 0 ||
			  strcmp(argv[argn], "--filesystem0") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_fs0, argv[argn], 255);
		}
		// -fs1, --filesystem1
		else if ((strcmp(argv[argn], "-fs1") == 0 ||
			  strcmp(argv[argn], "--filesystem1") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_fs1, argv[argn], 255);
		}
		// -fs2, --filesystem2
		/*
		else if ((strcmp(argv[argn], "-fs2") == 0 ||
			  strcmp(argv[argn], "--filesystem2") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_fs2, argv[argn], 255);
		}
		*/
		// -h, --help
		else if (strcmp(argv[argn], "-h") == 0 ||
			 strcmp(argv[argn], "--help") == 0) {
			usage(0, argv[0]);
		}
		// -k, --kernel
		else if ((strcmp(argv[argn], "-k") == 0 ||
			  strcmp(argv[argn], "--kernel") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_kernel, argv[argn], 255);
		}
		// -m, --maxlen
		else if ((strcmp(argv[argn], "-m") == 0 ||
			  strcmp(argv[argn], "--maxlen") == 0)
			  && argn + 1 < argc) {
			++argn;
			config_maxlen = (uint32_t)atoi(argv[argn]);
		}
		// -t, --trx
		else if ((strcmp(argv[argn], "-t") == 0 ||
			  strcmp(argv[argn], "--trx") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_trx, argv[argn], 255);
		}
		// -u, --userconf
		else if ((strcmp(argv[argn], "-u") == 0 ||
			  strcmp(argv[argn], "--userconf") == 0)
			  && argn + 1 < argc) {
			++argn;
			strncpy(config_userconf, argv[argn], 255);
		}
		// -V, --version
		else if (strcmp(argv[argn], "-V") == 0 ||
			 strcmp(argv[argn], "--version") == 0) {
			printf("%s v%s\n", vernam, vernum);
			exit(0);
		}
		// -xe, --extracte
		else if (strcmp(argv[argn], "-xe") == 0 ||
		    strcmp(argv[argn], "--extracte") == 0) {
			config_extract = TYPE_EXTENDED;
			action_type = config_extract;
		}
		// -xt, --extractt
		else if (strcmp(argv[argn], "-xt") == 0 ||
		    strcmp(argv[argn], "--extractt") == 0) {
			config_extract = TYPE_TRX;
			action_type = config_extract;
		}
		// doh
		else {
			printf("%s: Unrecognised command parameter [%s],"
			       " exiting.\n", vernam, argv[argn]);
			usage(1, argv[0]);
		}
		++argn;
	}
	if ( argn != argc )
		usage(1, argv[0]);
}




// Evil way to make main main
#ifdef WIN32
int _tmain(int argc, _TCHAR* argv[]) {
#else
int main(int argc, char **argv) {
#endif /* win32 */

	int maxlen;
	int bytes;
	int totlen;
	int trxlen;
	int fotlen;
	int fromtrx;
	int pullen;

#if O_BINARY
	_setmode (fileno(stdin), O_BINARY);
	_setmode (fileno(stdout), O_BINARY);
#endif
	/* obtain configuration state */
	parse_args(argc, argv);

	/* input validation check */
	if (input_validation() < 0) {
		return -1;
	}

	/* create memory region for firmware storage */
	if ((header = (struct belky_header *)malloc(config_maxlen)) == NULL) {
		printf("%s: unable to malloc [%d] bytes.\n",
			vernam, config_maxlen);
		if (header)
			free(header);
		return -1;
	}
	memset(header, 0, config_maxlen);

	/* v0.5 supports trx de/construction */
	theader = (struct trx_header *)((char *)header +
			sizeof(struct belky_header));

	/* v0.4 supports image extraction */
	if (config_extract) {

		if (action_type == TYPE_TRX) {
			maxlen = config_maxlen - sizeof(struct belky_header);
			/* read firmware from linux.trx file */
			bytes = read_in_data_file(config_trx,
				(void *)theader, maxlen, 1);
		} else {
			maxlen = config_maxlen;
			/* read firmware from firmware.belkin file */
			bytes = read_in_data_file(config_output,
				(void *)header, maxlen, 1);
		}
		if (bytes < 0) {
			return -1;
		}

		/* pull out extended files? */
		if (action_type == TYPE_EXTENDED) {

			/* setup the footer location */
			footer = (struct belky_footer *)((char *)header +
				 (uint32_t)PULL32_LE(theader->len) +
				 sizeof(struct belky_footer));

			/* pull out trx ?*/
			if (strlen(config_trx) !=0) {
				pullen = PULL32_LE(theader->len);
				write_firmware(config_trx, (char *)theader,
					pullen);
				if (bytes < 0) {
					return -1;
				}
			}

			/* pull out nvram ?*/
			if (strlen(config_userconf) !=0) {
				pullen = PULL32_LE(footer->len) -
					 sizeof(struct belky_footer);
				write_firmware(config_userconf,
					(char *)footer +
					sizeof(struct belky_footer),
					pullen);
				if (bytes < 0) {
					return -1;
				}
			}

		}

		/* pull out kernel ?*/
		if (strlen(config_kernel) !=0) {
			if (theader->offsets[0] == 0) {
				printf("%s: no kernel file offset found\n",
			       		vernam);
				return -1;
			}
			if (theader->offsets[1] != 0) {
				pullen = PULL32_LE(theader->offsets[1]) -
					 PULL32_LE(theader->offsets[0]);
			} else {
				pullen = PULL32_LE(theader->len) -
					 PULL32_LE(theader->offsets[0]);
			}
			write_firmware(config_kernel,
				(char *)theader + theader->offsets[0],
				pullen);
			if (bytes < 0) {
				return -1;
			}
		}

		/* pull out fs0 ?*/
		if (strlen(config_fs0) !=0) {
			if (theader->offsets[1] == 0) {
				printf("%s: no fs0 offset found\n",
			       		vernam);
				return -1;
			}
			if (theader->offsets[2] != 0) {
				pullen = PULL32_LE(theader->offsets[2]) -
					 PULL32_LE(theader->offsets[1]);
			} else {
				pullen = PULL32_LE(theader->len) -
					 PULL32_LE(theader->offsets[1]);
			}
			write_firmware(config_fs0,
				(char *)theader + theader->offsets[1],
				pullen);
			if (bytes < 0) {
				return -1;
			}
		}

		/* pull out fs1 ?*/
		if (strlen(config_fs1) !=0) {
			if (theader->offsets[2] == 0) {
				printf("%s: no fs1 offset found\n",
			       		vernam);
				return -1;
			}
			pullen = PULL32_LE(theader->len) -
				 PULL32_LE(theader->offsets[2]);
			write_firmware(config_fs1,
				(char *)theader + theader->offsets[2],
				pullen);
			if (bytes < 0) {
				return -1;
			}
		}


	} else {
		/* add the meat - build the trx component first */
		trxlen = 0;

		/* assumption based on no defaults .. */
		if (strlen(config_kernel) != 0) {

			/* not created from a trx file */
			fromtrx = 0;

			/* create trx from source files */
			trxlen = sizeof(struct trx_header);

			/* + kernel */
			theader->offsets[0] = STORE32_LE(trxlen);
			maxlen = config_maxlen - sizeof(struct belky_header) -
				 trxlen;
			if (action_type == TYPE_EXTENDED)
				maxlen = maxlen - sizeof(struct belky_footer);
			bytes = read_in_data_file(config_kernel,
				(void *)theader + trxlen, maxlen, 1);
			if (bytes < 0) {
				return -1;
			}

			/* structure alignment?  take ur word 4 it. */
			if (fs_count > 0) {
                        	if (bytes & (4-1)) {
                                	// memset(theader + trxlen + 4, 0,
						// 4 - (bytes & (4-1)));
                                	bytes += 4 - (bytes & (4-1));
                        	}
                	}
                	trxlen += bytes;

			if (strlen(config_fs0) != 0) {

				/* + fs0 */
				theader->offsets[1] = STORE32_LE(trxlen);
				maxlen = config_maxlen -
					 sizeof(struct belky_header) -
					 trxlen;
				if (action_type == TYPE_EXTENDED)
					maxlen = maxlen -
						 sizeof(struct belky_footer);
				bytes = read_in_data_file(config_fs0,
					(void *)theader + trxlen, maxlen, 1);
				if (bytes < 0) {
					return -1;
				}
			
				/* structure alignment?  take ur word 4 it. */
				if (fs_count > 1) {
       		                 	if (bytes & (4-1)) {
      		                         	memset(theader + trxlen + 4, 0,
							4 - (bytes & (4-1)));
						bytes += 4 - (bytes & (4-1));
                        		}
	                	}
				trxlen += bytes;
			}

			if (strlen(config_fs1) != 0) {

				/* + fs1 */
				theader->offsets[2] = STORE32_LE(trxlen);
				maxlen = config_maxlen -
					 sizeof(struct belky_header) -
					 trxlen;
				if (action_type == TYPE_EXTENDED)
					maxlen = maxlen -
						 sizeof(struct belky_footer);
				bytes = read_in_data_file(config_fs1,
					(void *)theader + trxlen, maxlen, 1);
				if (bytes < 0) {
					return -1;
				}
			
				/* structure alignment?  take ur word 4 it. */
				/*
				if (fs_count > 1) {
       		                 	if (bytes & (4-1)) {
      		                         	memset(theader + trxlen + 4, 0,
							4 - (bytes & (4-1)));
						bytes += 4 - (bytes & (4-1));
                        		}
	                	}
				*/
				trxlen += bytes;
			}

			bytes = trxlen & (0x1000 - 1);
			if (bytes) {
				// memset(theader + trxlen, 0, 0x1000 - bytes);
				trxlen += 0x1000 - bytes;
			}

		} else {

			/* created from a trx file */
			fromtrx = 1;

			/* read in the linux.trx file */
			maxlen = config_maxlen - sizeof(struct belky_header) -
				 sizeof(struct belky_footer);
			bytes = read_in_data_file(config_trx, 
				(void *)header + sizeof(struct belky_header),
				maxlen, 1);
			if (bytes < 0) {
				return -1;
			}
			trxlen = bytes;

		}

		if (action_type == TYPE_TRX) {

			/* total length of trx firmware */
			totlen = trxlen;

		} else {

			/* total length of extended firmware, so far */
			totlen = sizeof(struct belky_header) + trxlen;

			/* set the correct footer offset */
			footer = (struct belky_footer *)((char *)header +
				 (uint32_t)totlen);

			if (strlen(config_userconf) == 0) {
				printf("%s: extended firmware requested, "
					"NVRAM file is missing "
		       			"(try --help).\n", vernam);
				return -1;
			}

			/* read in the user.conf file */
			maxlen = config_maxlen - sizeof(struct belky_footer) -
				 totlen;
			bytes = read_in_data_file(config_userconf,
				(void *)footer + sizeof(struct belky_footer),
				maxlen, 1);
			if (bytes < 0) {
				return -1;
			}
			/* structure alignment?  take ur word 4 it. */
			/*
			if (bytes & (4-1)) {
				bytes += 4 - (bytes & (4-1));
                       	}
			*/
			fotlen = sizeof(struct belky_footer) + bytes;

			totlen += fotlen;

			/*
			fotlen = sizeof(footer->version) +
				 sizeof(footer->pad_0) +
				 sizeof(footer->pad_1) +
				 sizeof(footer->pad_2) + bytes;
			*/
		}

		/* time to colour in the picture */
		if (fill_headers((uint32_t)totlen, (uint32_t)trxlen,
			(uint32_t)fotlen, fromtrx) < 0) {
			return -1;
		}

		maxlen = totlen;

		/* write firmware to firmware.belkin file */
		if (action_type == TYPE_TRX) {
			bytes = write_firmware(config_trx, (char *)theader,
				maxlen);
		} else {
			bytes = write_firmware(config_output, (char *)header,
				maxlen);
		}
		if (bytes < 0) {
			return -1;
		}

	}

	return(0);
}


/**********************************************************************/
/* The following was grabbed and tweaked from the old snippets collection
 * of public domain C code. */
/**********************************************************************\
|* Demonstration program to compute the 32-bit CRC used as the frame  *|
|* check sequence in ADCCP (ANSI X3.66, also known as FIPS PUB 71     *|
|* and FED-STD-1003, the U.S. versions of CCITT's X.25 link-level     *|
|* protocol). The 32-bit FCS was added via the Federal Register,      *|
|* 1 June 1982, p.23798. I presume but don't know for certain that    *|
|* this polynomial is or will be included in CCITT V.41, which        *|
|* defines the 16-bit CRC (often called CRC-CCITT) polynomial. FIPS   *|
|* PUB 78 says that the 32-bit FCS reduces otherwise undetected       *|
|* errors by a factor of 10^-5 over 16-bit FCS.                       *|
\**********************************************************************/

/* Copyright (C) 1986 Gary S. Brown. You may use this program, or
   code or tables extracted from it, as desired without restriction.    */

/* First, the polynomial itself and its table of feedback terms. The    */
/* polynomial is                                                        */
/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0  */
/* Note that we take it "backwards" and put the highest-order term in   */
/* the lowest-order bit. The X^32 term is "implied"; the LSB is the     */
/* X^31 term, etc. The X^0 term (usually shown as "+1") results in      */
/* the MSB being 1.                                                     */

/* Note that the usual hardware shift register implementation, which    */
/* is what we're using (we're merely optimizing it by doing eight-bit   */
/* chunks at a time) shifts bits into the lowest-order term. In our     */
/* implementation, that means shifting towards the right. Why do we     */
/* do it this way? Because the calculated CRC must be transmitted in    */
/* order from highest-order term to lowest-order term. UARTs transmit   */
/* characters in order from LSB to MSB. By storing the CRC this way,    */
/* we hand it to the UART in the order low-byte to high-byte; the UART  */
/* sends each low-bit to hight-bit; and the result is transmission bit  */
/* by bit from highest- to lowest-order term without requiring any bit  */
/* shuffling on our part. Reception works similarly.                    */

/* The feedback terms table consists of 256, 32-bit entries. Notes:     */
/*                                                                      */
/* 1. The table can be generated at runtime if desired; code to do so   */
/*    is shown later. It might not be obvious, but the feedback         */
/*    terms simply represent the results of eight shift/xor opera-      */
/*    tions for all combinations of data and CRC register values.       */
/*                                                                      */
/* 2. The CRC accumulation logic is the same for all CRC polynomials,   */
/*    be they sixteen or thirty-two bits wide. You simply choose the    */
/*    appropriate table. Alternatively, because the table can be        */
/*    generated at runtime, you can start by generating the table for   */
/*    the polynomial in question and use exactly the same "updcrc",     */
/*    if your application needn't simultaneously handle two CRC         */
/*    polynomials. (Note, however, that XMODEM is strange.)             */
/*                                                                      */
/* 3. For 16-bit CRCs, the table entries need be only 16 bits wide;     */
/*    of course, 32-bit entries work OK if the high 16 bits are zero.   */
/*                                                                      */
/* 4. The values must be right-shifted by eight bits by the "updcrc"    */
/*    logic; the shift must be unsigned (bring in zeroes). On some      */
/*    hardware you could probably optimize the shift in assembler by    */
/*    using byte-swap instructions.                                     */

static const uint32_t crc_32_tab[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

#define UPDC32(octet,crc) (crc_32_tab[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8))

uint32_t crc32buf(char *buf, size_t len)
{
	uint32_t crc;
	crc = 0xFFFFFFFF;
	for ( ; len; --len, ++buf)
	{
		crc = UPDC32(*buf, crc);
	}
	return crc;
}

