#include #include #include #include #include #include "get_address.h" #include "xex.h" #ifndef VERSION #define VERSION "???" #endif #define SELF "xexcat" #define OPTIONS "hvo:l:r:i:cf:a" 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] [-f 1|2 ] [-l address] [-i address] [-r address]\n" " [-o outfile.xex] [infile1.xex] [infile2.xex ...]\n" " -o outfile.xex Output file (default: standard output)\n" " -h Print this help\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" " -a Force run address to load address of 1st segment.\n" " -f 1|2 Input is DASM -f1 or -f2 format, not xex.\n"; static int readaddr(FILE *f) { int i, j; if( (i = fgetc(f)) < 0 ) return -1; if( (j = fgetc(f)) < 0 ) return -1; return i | (j << 8); } /* byte 0 = LSB of load address, byte 1 = MSB, rest = data. */ int read_f1(xex_segment *seg, FILE *f) { int i, addr, c; addr = readaddr(f); if(addr < 0) return 0; seg->start_addr = addr; for(i = 0; (i < 65536) && ((c = fgetc(f)) >= 0); i++) { seg->object[i] = c; seg->len++; } /* guard against the file being >64K length */ fseek(f, 0, SEEK_END); seg->end_addr = seg->start_addr + seg->len - 1; seg->has_ff_header = 1; return (i != 0); } /* -f2 is segmented, each segment starts with a 4-byte header: 0/1 are LSB/MSB of start address, 2/3 are LSB/MSB of length. */ int read_f2(xex_segment *seg, FILE *f) { int i, addr, c; addr = readaddr(f); if(addr < 0) return 0; seg->start_addr = addr; addr = readaddr(f); if(addr < 0) return 0; seg->len = addr; for(i = 0; (i < seg->len) && ((c = fgetc(f)) >= 0); i++) { if(c < 0) return 0; seg->object[i] = c; } seg->end_addr = seg->start_addr + seg->len - 1; seg->has_ff_header = 1; xex_print_seg_info(seg); return 1; } int read_seg(xex_segment *seg, FILE *f, int type) { switch(type) { case 1: return read_f1(seg, f); case 2: return read_f2(seg, f); case 0: default: return xex_fread_seg(seg, f); } } 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, run_1st_seg = 0; int input_type = 0; /* 0 = xex, 1 and 2 are dasm -f1/-f2. */ /* 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; case 'f': input_type = atoi(optarg); if(input_type < 1 || input_type > 2) { fprintf(stderr, SELF ": invalid -f argument (must be 1 or 2).\n"); exit(1); } break; case 'a': run_1st_seg = 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(read_seg(&seg, in, input_type)) { if(filecount++ == 1 && !seg.has_ff_header) { fprintf(stderr, SELF ": warning: '%s' first segment " "lacks $FFFF header (bad/partial XEX file?)\n", infile); } if(run_1st_seg && (force_run == -1)) { force_run = seg.start_addr; if(xex_verbose) fprintf(stderr, SELF ": %s: setting run address to %04X " "due to -a option\n", infile, force_run); } /* 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; }