diff options
Diffstat (limited to 'a8xd.c')
-rw-r--r-- | a8xd.c | 400 |
1 files changed, 400 insertions, 0 deletions
@@ -0,0 +1,400 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <locale.h> +#include <wchar.h> +#include <errno.h> + +#include "atables.h" + +/* printf() formats for lower/uppercase hex. */ +#define LC_WORD_FMT "%04x" +#define LC_BYTE_FMT "%02x" +#define UC_WORD_FMT "%04X" +#define UC_BYTE_FMT "%02X" + +/* colors. don't use black or white: one or the other is likely + the terminal's text color already. + don't use blue because it's hard to read. */ +#define C_RED 1 +#define C_GREEN 2 +#define C_YELLOW 3 +#define C_PURPLE 5 +#define C_CYAN 6 + +/* translation table (see atables.c), choices are + ata2utf, ics2utf, or magazine. */ +const char **table = ata2utf; + +/* hardcoded ANSI-style escape sequences, work fine on + all modern terminal emulators. */ +const char *inverse_on = "\x1b[7m"; +const char *inverse_off = "\x1b[0m"; +const char *color_off = "\x1b[0m"; + +/* -u option changes these. */ +const char *word_format = LC_WORD_FMT; +const char *byte_format = LC_BYTE_FMT; + +/* command line options change these. */ +int verbose = 0, color = 1, disp_offset = 0, maxlen = 0; +int seek_whence = 0, seekpos = 0, filepos = 0, limit = 0; +int graphics = 0, screencodes = 0, high_font = 0; + +const char *self; + +void set_self(const char *argv0) { + char *p; + + self = argv0; + p = strrchr(self, '/'); + if(p) self = p + 1; +} + +void die(const char *msg) { + fprintf(stderr, "%s: %s\n", self, msg); + exit(1); +} + +void print_help(void) { + printf("Usage:\n %s [-a] [-f] [-g] [-i] [-l limit] [-m] [-o offset] [-s [-]seek] [-u] [-v] [file]\n", self); + printf(" -a: Input is ANTIC screencodes (default is ATASCII).\n"); + printf(" -g: GR.1/2 style colorization.\n"); + printf(" -i: Input is XL intl charset (default is ATASCII).\n"); + printf(" -l: Stop after <limit> bytes.\n"); + printf(" -m: Monochrome (color off).\n"); + printf(" -o: Add <offset> to displayed byte positions.\n"); + printf(" -s: Seek <seek> bytes into input (with -, seek back from EOF).\n"); + printf(" -u: Uppercase hex digits (default is lowercase).\n"); + printf(" -v: Verbose debugging info.\n"); + printf("With no [file], or -, reads from stdin.\n"); +} + +int parse_num_arg(const char *arg) { + int got; + + if(sscanf(arg, "0x%x", &got) != 1) + if(sscanf(arg, "$%x", &got) != 1) + if(sscanf(arg, "%d", &got) != 1) { + fprintf(stderr, "%s: invalid numeric argument '%s'.", self, arg); + exit(1); + } + + return got; +} + +void parse_seek_arg(const char *arg) { + seek_whence = SEEK_SET; + + if(*arg == '-') { + seek_whence = SEEK_END; + } + + seekpos = parse_num_arg(arg); +} + +int parse_offset_arg(const char *arg) { + int got; + + got = parse_num_arg(arg); + + return got; +} + +int parse_limit_arg(const char *arg) { + int got; + + got = parse_num_arg(arg); + if(got < 0) + die("negative arguments to -l (limit) are not supported."); + + return got; +} + +FILE *open_input(const char *file) { + FILE *input; + + if(file[0] == '-' && file[1] == 0) { + if(freopen(NULL, "rb", stdin)) { + input = stdin; + } else { + fprintf(stderr, "%s: ", self); + perror("(standard input)"); + return NULL; + } + if(verbose) + fprintf(stderr, "%s: reading from standard input.\n", self); + } else { + if(!(input = fopen(file, "rb"))) { + perror(file); + return NULL; + } + if(verbose) + fprintf(stderr, "%s: reading from file '%s'.\n", self, file); + } + + return input; +} + +int get_text_color(unsigned char c) { + unsigned char c7 = c & 0x7f; + + if(c == 0 || c == 0x9b) { + return C_RED; + } else if((c7 >= 0x1b && c7 <= 0x1f) || (c7 >= 0x7d)) { + /* cursor control characters: + - 0x1b (esc) + - 0x1c-0x1f (arrows), 0x9c-0x9f + - 0x7d/0xf3 (cls, bell) + - 0x7e, 0xfe (delete line, char) + - 0x7f, 0xff (tab, insert char) + */ + return C_PURPLE; + } else if(c7 < 32 || c7 == 0x60 || c7 == 0x7b) { + return C_YELLOW; + } else { + return C_GREEN; + } +} + +int get_graphics_color(unsigned char c) { + if(c < 0x20) + return C_GREEN; + else if(c >= 0x20 && c < 0x60) + return C_YELLOW; + else if(c >= 0x60 && c < 0x80) + return C_GREEN; + else if(c >= 0x80 && c < 0xa0) + return C_RED; + else if(c >= 0xa0 && c < 0xe0) + return C_CYAN; + else /* c >= 0xe0 */ + return C_RED; +} + +char *get_color(unsigned char c) { + int color; + static char outbuf[32]; + + if(graphics) { + color = get_graphics_color(c); + } else { + color = get_text_color(c); + } + + if(color) { + sprintf(outbuf, "\x1b[0;3%dm", color); + } else { + outbuf[0] = '\0'; + } + + return outbuf; +} + +/* displaying ATASCII (not screencodes) in GR.1 or GR.2: + $00-$1F get displayed in green, as $20-$2F. + $20-$5F get displayed in orange, as themselves. + $60-$7F get displayed in green, as $40-$5F. + for $80-$FF, same thing, but green is red, and orange is blue. +*/ +unsigned char get_graphics(unsigned char c) { + c &= 0x7f; + if(c < 0x20) + c += 0x20; + else if(c >= 0x60) + c -= 0x20; + return c; +} + +unsigned char get_high_graphics(unsigned char c) { + c &= 0x7f; + if(c >= 0x20 && c < 0x40) + c -= 0x20; + else if(c >= 0x40) + c += 0x20; + return c; +} + +unsigned char screen2ata(unsigned char c) { + unsigned char c7 = c & 0x7f; + + if(c7 < 0x40) + return c + 0x20; + if(c7 >= 0x40 && c7 < 0x60) + return c - 0x40; + return c; +} + +void fake_seek(FILE *input) { + char junkbuf[1024]; + int pos = 0, chunksize; + + if(verbose) + fprintf(stderr, "%s: faking fseek() for stdin, skipping %d bytes.\n", self, seekpos); + + while(pos < seekpos) { + chunksize = seekpos - pos; + if(chunksize > 1024) chunksize = 1024; + if(fread(junkbuf, 1, chunksize, input) != chunksize) + die("can't seek past end of input"); + pos += chunksize; + } +} + +void seek_input(FILE *input) { + if(verbose) + fprintf(stderr, "%s: seeking %d bytes (whence = %d) in input.\n", self, seekpos, seek_whence); + if(input == stdin) { + /* can't fseek() a stream, fake it */ + if(seek_whence != SEEK_SET) + die("can't seek from the end of standard input."); + fake_seek(input); + } else { + if(fseek(input, seekpos, seek_whence) < 0) { + fprintf(stderr, "%s: ", self); + perror("fseek() failed"); + exit(1); + } + if((filepos = ftell(input)) < 0) { + fprintf(stderr, "%s: ", self); + perror("ftell() failed"); + exit(1); + } + } +} + +void append_str_f(char *buf, int *pos, const char *fmt, const char *str) { + *pos += sprintf(buf + *pos, fmt, str); +} + +void append_str(char *buf, int *pos, const char *str) { + append_str_f(buf, pos, "%s", str); +} + +void append_hex(char *buf, int *pos, const unsigned char byte) { + *pos += sprintf(buf + *pos, byte_format, byte); +} + + +void dump_line(const unsigned char *buf, int len) { + char hex[1024], asc[1024]; + int hpos = 0, apos = 0, count = len; + unsigned char c, inv; + + memset(hex, 0, sizeof(hex)); + memset(asc, 0, sizeof(asc)); + + printf(word_format, filepos + disp_offset); + fputs(": ", stdout); + + while(len) { + c = *buf; + inv = !graphics && (c & 0x80); + if(screencodes) c = screen2ata(c); + + hpos += printf("%s", get_color(c)); + if(inv) hpos += printf("%s", inverse_on); + hpos += printf(byte_format, *buf); /* *buf here, not c! */ + if(color || inv) hpos += printf("%s", color_off); + putchar(' '); + hpos++; + if(count - len == 7) { + putchar(' '); + hpos++; + } + + if(color) append_str(asc, &apos, get_color(c)); + if(inv) append_str(asc, &apos, inverse_on); + if(graphics) { + if(high_font) + c = get_high_graphics(c); + else + c = get_graphics(c); + } + apos += sprintf(asc + apos, "%s", table[c & 0x7f]); + if(color || inv) append_str(asc, &apos, color_off); + + filepos++; + buf++; + len--; + } + + /* what shall we use to fill the empty spaces? */ + if(count < 8) putchar(' '); + for(; count < 16; count++) fputs(" ", stdout); + + printf(" %s\n", asc); +} + +int a8xd(const char *file) { + FILE *input; + int c, len = 0, count = 0; + unsigned char buf[16]; + + if( !(input = open_input(file)) ) + return 1; + + if(seekpos) seek_input(input); + + while( (c = fgetc(input)) != EOF ) { + if(limit && (count == limit)) break; + count++; + if(len && (len % 16 == 0)) { + dump_line(buf, len); + len = 0; + } + buf[len++] = c; + } + + if(len) dump_line(buf, len); + + if(verbose) + fprintf(stderr, "%s: dumped %d (0x%04x) bytes from '%s'.\n", self, count, count, file); + + fclose(input); + return 0; +} + +int main(int argc, char **argv) { + int opt, result = 0; + + set_self(argv[0]); + + if(argc > 1 && strcmp(argv[1], "--help") == 0) { + print_help(); + exit(0); + } + + if(argc > 1 && strcmp(argv[1], "--version") == 0) { + printf("%s %s\n", self, VERSION); + exit(0); + } + + while( (opt = getopt(argc, argv, "vhimus:o:l:gaf")) != -1) { + switch(opt) { + case 'v': verbose = 1; break; + case 'h': print_help(); exit(0); break; + case 'i': table = ics2utf; break; + case 'm': color = 0; break; + case 'u': word_format = UC_WORD_FMT; byte_format = UC_BYTE_FMT; break; + case 's': parse_seek_arg(optarg); break; + case 'o': disp_offset = parse_offset_arg(optarg); break; + case 'l': limit = parse_limit_arg(optarg); break; + case 'g': graphics = 1; break; + case 'a': screencodes = 1; break; + case 'f': graphics = high_font = 1; break; + default: print_help(); exit(1); break; + } + } + + if(optind < argc - 1) + die("Only one filename is supported."); + + if(optind >= argc) + result = a8xd("-"); + else + result = a8xd(argv[optind]); + + exit(result); +} |