aboutsummaryrefslogtreecommitdiff
path: root/a8xd.c
diff options
context:
space:
mode:
Diffstat (limited to 'a8xd.c')
-rw-r--r--a8xd.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/a8xd.c b/a8xd.c
new file mode 100644
index 0000000..1a48312
--- /dev/null
+++ b/a8xd.c
@@ -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);
+}