aboutsummaryrefslogtreecommitdiff
path: root/xexcat.c
diff options
context:
space:
mode:
Diffstat (limited to 'xexcat.c')
-rw-r--r--xexcat.c248
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;
+}