diff options
Diffstat (limited to 'xexcat.c')
-rw-r--r-- | xexcat.c | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/xexcat.c b/xexcat.c new file mode 100644 index 0000000..22d8276 --- /dev/null +++ b/xexcat.c @@ -0,0 +1,248 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include "get_address.h" +#include "xex.h" + +#ifndef VERSION +#define VERSION "???" +#endif + +#define SELF "xexcat" +#define OPTIONS "hvo:l:r:i:c" + +char *usage = + SELF " v" VERSION " - by B. Watson (WTFPL)\n" + "Join one or more Atari 8-bit executables into one multi-segment file.\n" + "usage: " SELF " -[hvc] [-l address] [-i address] [-r address]\n" + " [-o outfile.xex] [infile1.xex] [infile2.xex ...]\n" + " -h Print this help\n" + " -o outfile.xex Output file (default: standard output)\n" + " -v Verbose operation\n" + " -c Check only; no output (same as -v -o/dev/null)\n" + " -l address Force first load address (decimal, $hex, or 0xhex)\n" + " -i address Force first init address\n" + " -r address Force run address\n"; + +int main(int argc, char **argv) { + xex_segment seg; + char *outfile = "-"; + FILE *in, *out = stdout; + int count = 1, c, errcount = 0; + unsigned char buffer[65536]; /* be lazy: statically allocate large buffer */ + int force_load = -1, force_run = -1, force_init = -1; + int read_stdin = 0; + + /* parse args */ + while( (c = getopt(argc, argv, OPTIONS)) > 0) { + switch(c) { + case 'h': + printf(usage); + exit(0); + break; + + case 'v': + xex_verbose = 1; + break; + + case 'o': + outfile = optarg; + break; + + case 'c': + xex_verbose = 1; + outfile = "/dev/null"; + break; + + case 'l': + if( (force_load = get_address(SELF, optarg)) < 0 ) + exit(1); + break; + + case 'r': + if( (force_run = get_address(SELF, optarg)) < 0 ) + exit(1); + break; + + case 'i': + if( (force_init = get_address(SELF, optarg)) < 0 ) + exit(1); + break; + + default: + fprintf(stderr, usage); + exit(1); + } + } + + /* special case if there are no input filenames */ + if(argc <= optind) { + read_stdin = 1; + } + + /* open outfile */ + if(strcmp(outfile, "-") != 0) { + if( !(out = fopen(outfile, "wb")) ) { + fprintf(stderr, SELF ": %s: %s\n", outfile, strerror(errno)); + exit(1); + } + } else if(isatty(fileno(out))) { + /* be polite... */ + fprintf(stderr, + SELF ": Standard output is a terminal; not writing binary data\n" + "Run '" SELF " -h' for help\n"); + exit(1); + } else { + outfile = "(standard output)"; + } + + /* only have to set seg.object once... */ + seg.object = buffer; + + /* process each input file on the command line */ + while(read_stdin || (optind < argc)) { + char *infile = argv[optind++]; + int filecount = 1; + + if(read_stdin || strcmp(infile, "-") == 0) { + read_stdin = 0; + in = stdin; + infile = "(standard input)"; + } else if( !(in = fopen(infile, "rb")) ) { + /* failure to open is NOT fatal (just skip it and move on) */ + fprintf(stderr, SELF ": %s: %s\n", infile, strerror(errno)); + errcount++; + continue; + } + + if(xex_verbose) + fprintf(stderr, SELF ": reading from file %s\n", infile); + + /* process every segment in current input file */ + while(xex_fread_seg(&seg, in)) { + if(filecount++ == 1 && !seg.has_ff_header) { + fprintf(stderr, SELF ": warning: '%s' first segment " + "lacks $FFFF header (bad/partial XEX file?)\n", + infile); + } + + /* normalize the $FFFF headers: only the first segment needs one */ + seg.has_ff_header = (count == 1); + + /* process -l option */ + if(count == 1 && force_load > -1) { + if(xex_verbose) + fprintf(stderr, + SELF ": %s: setting first load address to %04X " + "due to -l option\n", + infile, force_load); + seg.start_addr = force_load; + seg.end_addr = force_load + seg.len - 1; + force_load = -1; + } + + count++; + + /* process -i option */ + if(seg.start_addr == XEX_INITAD && seg.len == 2) { + if(force_init == 0) { + if(xex_verbose) + fprintf(stderr, + SELF ": %s: " + "skipping first init address segment due to -i0\n", + infile); + force_init = -1; + continue; + } else if(force_init > 0) { + if(xex_verbose) + fprintf(stderr, + SELF ": %s: " + "setting first init address to %04X due to -i option\n", + infile, force_init); + seg.object[0] = XEX_LSB(force_init); + seg.object[1] = XEX_MSB(force_init); + force_init = -1; + } + + if(xex_verbose) + fprintf(stderr, + SELF ": %s: init address: %04X\n", + infile, XEX_ADDR(seg.object[0], seg.object[1])); + } + + /* process -r option */ + if(seg.start_addr == XEX_RUNAD && seg.len == 2) { + if(force_run > -1) { + if(xex_verbose) + fprintf(stderr, + SELF ": %s: " + "skipping run address segment due to -r option\n", + infile); + continue; + } else { + if(xex_verbose) + fprintf(stderr, + SELF ": %s: " + "run address: %04X\n", + infile, XEX_ADDR(seg.object[0], seg.object[1])); + } + } + + /* write (possibly modified) segment to output */ + if(!xex_fwrite_seg(&seg, out)) { + fprintf(stderr, SELF ": %s: %s\n", + outfile, xex_strerror(xex_errno)); + xex_errno = 0; + errcount++; + break; + } + } + + /* xex_errno will be 0 for XERR_NONE (meaning "no error") */ + if(xex_errno) { + fprintf(stderr, SELF ": %s: %s\n", + infile, xex_strerror(xex_errno)); + errcount++; + } else if(filecount == 1) { + fprintf(stderr, SELF ": warning: %s: file is empty.\n", infile); + } + + if(xex_verbose) + fprintf(stderr, SELF ": done reading file %s\n", infile); + + fclose(in); + } + + /* if -r given, all run addresses in all files were omitted from the + output file. Here we add a single run address to the output. */ + if(force_run > 0) { + xex_run_seg(&seg, buffer, force_run); + if(!xex_fwrite_seg(&seg, out)) { + fprintf(stderr, SELF ": %s: %s\n", + outfile, xex_strerror(xex_errno)); + errcount++; + } + count++; + } else if(count == 1) { + fprintf(stderr, SELF ": warning: %s: file is empty.\n", outfile); + } + + if(xex_verbose) + fprintf(stderr, SELF ": wrote %d segment%s to %s\n", + (count - 1), + (count == 2 ? "" : "s"), + outfile); + + fclose(out); + + if(xex_verbose || errcount) { + fprintf(stderr, + SELF ": %d error%s.\n", errcount, (errcount == 1 ? "" : "s")); + return errcount; + } + + return 0; +} |