/* BinToObj -- Version 1.0 -- Copyright (C) 1991 by Ray Burr Converts a raw binary file to an Amiga Hunk format object file. */ /* Copyright Notice (I'd use the GNU GPL but it's bigger than this source file): This program may be freely copied and redistributed under the condition that the source code and documentation is distributed with it. Modified versions may be distributed if this notice is left here unchanged and the history, including credit to the original author, is clearly documented. */ /* HISTORY: (911201 ryb) Created. */ #include /* Needed for size_t in Lattice C Version 5.04 */ #include #include #include /* Hacks to make perfect :-) ANSI C code work under Lattice C. */ #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #define EXIT_FAILURE 20 #endif #ifndef SEEK_END #define SEEK_END 2 #endif /* More space efficient than isdigit() in some C library implemintations. */ #define CHAR_DIGIT_P(c) ((c) >= '0' && (c) <= '9') #define HUNK_UNIT 0x03e7 #define HUNK_DATA 0x03ea #define HUNK_EXT 0x03ef #define HUNK_END 0x03f2 #define EXT_DEF 0x01 #define EXT_ABS 0x02 #define HUNK_FAST (1 << 31) #define HUNK_CHIP (1 << 30) #define TRUE 1 #define FALSE 0 #define BUFFER_SIZE 4096 char *program_name = "BinToHunk"; /* Common error messages. */ char *write_error_msg = "Error writing output file"; char *length_error_msg = "Couldn't determine input file's length"; /* Define `BOOTSTRAP' if you don't already have a working BinToHunk executable to convert usage.txt to usage.o. */ #ifndef BOOTSTRAP /* Usage message in file usage.o which was created by BinToHunk. (Couldn't resist it.) */ extern char usage_message[]; #else /* Temporary definition to make linker happy. */ char *usage_message = "*** BOOTSTRAP Version ***\n"; #endif /* Print an error message after in I/O error and exit with error status. */ static void pfail (const char *message) { fprintf (stderr, "%s: ", program_name); perror (message); exit (EXIT_FAILURE); } /* Print an error message and exit with error status. */ static void fail (const char *message) { fprintf (stderr, "%s: %s", program_name, message); exit (EXIT_FAILURE); } /* Allocate memory and handle failure. */ static void * xmalloc (size_t size) { void *block; block = malloc (size); if (!block) fail ("Couldn't allocate memory"); return block; } /* Write the 32-bit value LONGWORD to the stream STREAM in big-endian byte order and handle errors. */ #ifdef __GNUC__ __inline__ #endif static void putl (FILE *stream, long longword) { long lw = longword; /* This assumes big endian byte order. */ if (fwrite (&lw, sizeof (long), 1, stream) < 1) pfail (write_error_msg); } /* Write LENGTH bytes of VALUE to OUTFILE. */ #ifdef __GNUC__ __inline__ #endif static void write_pad (FILE *outfile, int value, size_t length) { while (length-- > 0) if (fputc (value, outfile) == EOF) pfail (write_error_msg); } /* Write LENGTH bytes to OUTFILE representing TERMINATOR in big-endian byte order. TERMINATOR is a two's-compliment signed value and will be sign extended. */ #ifdef __GNUC__ __inline__ #endif static void write_terminator (FILE *outfile, int terminator, int length) { if (length == 0) return; if (length > sizeof (int)) { write_pad (outfile, (terminator < 0 ? -1 : 0), length - sizeof (int)); length = 4; } /* This assumes big endian byte order. */ if (fwrite (&terminator + 4 - length, 1, length, outfile) < length) pfail (write_error_msg); } /* Determine length of file STREAM was opened with. Moves file position to the begining. Exits with a message if it is unsuccessful. */ static size_t file_length (FILE *stream) { long length; if (fseek (stream, 0, SEEK_END)) pfail (length_error_msg); length = ftell (stream); if (length < 0) pfail (length_error_msg); rewind (stream); return (size_t) length; } /* Copy INFILE to OUTFILE until INFILE reaches end-of-file. This function allocates a buffer who's size is determined by the BUFFER_SIZE macro symbol. I/O errors cause a call to pfail(). */ static void copy_data (FILE *infile, FILE *outfile) { void *buffer; size_t bytes_read; buffer = xmalloc (BUFFER_SIZE); while (!feof (infile)) { bytes_read = fread (buffer, 1, BUFFER_SIZE, infile); if (ferror (infile)) pfail ("Error reading input file"); if (bytes_read <= 0) continue; fwrite (buffer, 1, bytes_read, outfile); if (ferror (outfile)) pfail (write_error_msg); } free (buffer); } /* Write NAME to OUTFILE as a name in the format used in Amiga Hunk object files; a longword of length in longwords, and a name padded to a longword boundry with zeros. EXT_TYPE is a value that will be put in the first (most significant) byte of the length longword. */ static void write_name (FILE *outfile, const char *name, int ext_type) { size_t name_length, pad_length; name_length = strlen (name); pad_length = 3 - (name_length + 3) % 4; putl (outfile, ((name_length + 3) / 4) | (ext_type << 24)); if (fwrite (name, 1, name_length, outfile) < name_length) pfail (write_error_msg); write_pad (outfile, 0, pad_length); } /* Display a usage message and exit with success status. */ static void usage (void) { fputs (usage_message, stderr); exit (EXIT_SUCCESS); } /* Displays MESSAGE, if it is non-NULL, and a message saying how to get usage information. Exits with failure status. */ static void fail_with_usage (const char *message) { if (message) fprintf (stderr, "%s: %s\n", program_name, message); fprintf (stderr, "Type `%s -?' for usage.\n", program_name); exit (EXIT_FAILURE); } /* Point of entry. */ void main (int argc, char *argv[]) { FILE *infile, *outfile; /* Streams for the input and output files. */ char *infile_name; /* Name of the input file. */ char *outfile_name; /* Name of the output file. */ char *block_ident; /* Name of the indentifier for the array. */ char *block_length_ident; /* Name of the indentifier specifying the length of the array. */ long terminator; /* The value of the terminator object. */ long term_length; /* Number of bytes in the terminator. Zero means no terminator. */ long memory_mode; /* Tells the loader where to load the hunk. */ int nolength_flag; /* Flag: Don't define a length symbol. */ int absolute_flag; /* Flag: Use an absolute symbol definition for the length. */ char *base; /* Base of input filename. (dir/base.txt) */ size_t base_length; /* Length of base input filename. */ int i, arg_count; /* Counters for arg processing. */ size_t pad_length; /* Number of bytes to make data an even number of longwords. */ size_t length; /* The length of the input file in bytes. */ size_t object_size; /* The size of one item in the array. */ size_t num_objects; /* Number of objects read in. */ size_t total_length; /* Length of data before longword padding. */ int object_pad_length; /* Number of bytes to pad data to an even number of objects. */ int data_lw_length; /* Total number of longwords in data. */ /* Set some defaults. */ nolength_flag = FALSE; absolute_flag = FALSE; term_length = 0; terminator = 0; object_size = 1; memory_mode = 0; infile_name = NULL; outfile_name = NULL; block_ident = NULL; block_length_ident = NULL; /* Format users hard disk if no arguments are given. */ if (argc <= 1) usage (); /* Parse argument line. */ arg_count = 0; for (i = 1; i < argc; ++i) { if (!strcmp (argv[i], "?")) usage (); if (argv[i][0] == '-') { /* Deal with options. */ switch (argv[i][1]) { case 'c': case 'C': memory_mode |= HUNK_CHIP; goto no_arg; case 'f': case 'F': memory_mode |= HUNK_FAST; goto no_arg; case 'l': case 'L': nolength_flag = TRUE; goto no_arg; case 'a': case 'A': absolute_flag = TRUE; goto no_arg; case 't': case 'T': term_length = -1; /* Turns into the length of one object. */ terminator = 0; /* The default. */ if (argv[i][2] == '\0') break; if (!CHAR_DIGIT_P (argv[i][2]) && !(argv[i][2] == '-' && CHAR_DIGIT_P (argv[i][3]))) fail_with_usage ("Invalid argument to -t"); terminator = atoi (argv[i] + 2); break; case 's': case 'S': if (!CHAR_DIGIT_P (argv[i][2])) fail_with_usage ("Invalid argument to -s"); object_size = atoi (argv[i] + 2); if (object_size <= 0) fail_with_usage ("Bad value for -s option"); break; case '?': usage (); break; no_arg: /* Verify that no argument to the option was given. */ if (argv[i][2] != 0) { fprintf (stderr, "%s: Option `-%c' does not take an argument\n", program_name, argv[i][1]); fail_with_usage (NULL); } break; case '\0': default: fprintf (stderr, "%s: Bad option `%s'\n", program_name, argv[i]); fail_with_usage (NULL); break; } } else { /* Assign non-option arguments to variables. */ switch (arg_count) { case 0: infile_name = argv[i]; break; case 1: outfile_name = argv[i]; break; case 2: block_ident = argv[i]; break; case 3: block_length_ident = argv[i]; break; default: fail_with_usage ("Too many arguments"); break; } ++arg_count; } } if (arg_count == 0) fail_with_usage ("No output filename"); /* A negative TERM_LENGTH means use units of object_size bytes. */ if (term_length < 0) term_length = object_size * -term_length; /* Find the base name of INFILE_NAME. */ base = strrchr (infile_name, '/'); if (base == NULL) base = strrchr (infile_name, ':'); if (base == NULL) base = infile_name; /* Find the length of the base name of INFILE_NAME. */ { char *dot; dot = strrchr (infile_name, '.'); if (dot == NULL) base_length = strlen (base); else base_length = dot - base; } /* Default output filename is BASE with ".o" appened. */ if (outfile_name == NULL) { outfile_name = xmalloc (base_length + 3); memcpy (outfile_name, base, base_length); strcpy (outfile_name + base_length, ".o"); } /* Default BLOCK_IDENT is BASE with "_" prepended. */ if (block_ident == NULL) { block_ident = xmalloc (base_length + 2); block_ident[0] = '_'; memcpy (block_ident + 1, base, base_length); block_ident[base_length + 1] = '\0'; } /* Default BLOCK_LENGTH_IDENT is BASE with "_" prepended and "_length" appended. */ if (block_length_ident == NULL && !nolength_flag) { block_length_ident = xmalloc (strlen (block_ident) + 8); strcpy (block_length_ident, block_ident); strcat (block_length_ident, "_length"); } /* Open output file in binary mode. */ outfile = fopen (outfile_name, "wb"); if (outfile == NULL) pfail ("Can't open output file"); /* Start un-named program unit. */ putl (outfile, HUNK_UNIT); putl (outfile, 0); /* Start data block. */ putl (outfile, HUNK_DATA); /* Open input file in binary mode. */ infile = fopen (infile_name, "rb"); if (infile == NULL) pfail ("Can't open input file"); /* Find length of input file. */ length = file_length (infile); /* Calculate the number of objects. Any bytes left over are counted as an object. */ num_objects = (length + object_size - 1) / object_size; /* Calculate the number of bytes needed to make the array's length an even multiple of OBJECT_SIZE. */ object_pad_length = num_objects * object_size - length; /* Find the total length of all the data used in the data block. */ total_length = length + object_pad_length + term_length; /* Find the total number of longwords in the data block. */ data_lw_length = (total_length + 3) / 4; /* Find the number of bytes needed to make the data block an even number of longwords. */ pad_length = data_lw_length * 4 - total_length; /* Account for length variable. */ if (block_length_ident != NULL && !absolute_flag) data_lw_length += 1; /* Output the length of the data block and attach memory attribute flags. */ putl (outfile, data_lw_length | memory_mode); /* If needed, output the length of the array in objects as the first longword of the data block. */ if (block_length_ident && !absolute_flag) putl (outfile, num_objects); /* Copy the array from the input file. */ copy_data (infile, outfile); /* Close the input file. */ if (fclose (infile)) pfail ("Error closing input file"); /* Align to object size. */ write_pad (outfile, 0, object_pad_length); /* Write the (possibly zero length) terminator. */ write_terminator (outfile, terminator, term_length); /* Align to longword. */ write_pad (outfile, 0, pad_length); /* Now start the external symbol block. */ putl (outfile, HUNK_EXT); /* Write a symbol data unit defining BLOCK_IDENT as a symbol relative to the start of the data block. */ write_name (outfile, block_ident, EXT_DEF); putl (outfile, (block_length_ident && !absolute_flag) ? 4 : 0); /* If an array length symbol is wanted... */ if (block_length_ident) { if (absolute_flag) { /* Write a symbol data unit defining BLOCK_LENGTH_IDENT as an absolute symbol defining the number of objects. */ write_name (outfile, block_length_ident, EXT_ABS); putl (outfile, num_objects); } else { /* Write a symbol data unit defining BLOCK_LENGTH_IDENT as a symbol relative to the start of the data block. */ write_name (outfile, block_length_ident, EXT_DEF); putl (outfile, 0); } } /* End the external symbol block. */ putl (outfile, 0); /* End the hunk and the program unit. */ putl (outfile, HUNK_END); /* Close the output file. */ if (fclose (outfile)) pfail ("Error closing output file"); /* Later. */ exit (EXIT_SUCCESS); }