| /* hexedit.c - Hexadecimal file editor |
| * |
| * Copyright 2015 Rob Landley <rob@landley.net> |
| * |
| * No standard |
| |
| USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) |
| |
| config HEXEDIT |
| bool "hexedit" |
| default y |
| help |
| usage: hexedit FILENAME |
| |
| Hexadecimal file editor. All changes are written to disk immediately. |
| |
| -r Read only (display but don't edit) |
| |
| Keys: |
| Arrows Move left/right/up/down by one line/column |
| Pg Up/Pg Dn Move up/down by one page |
| 0-9, a-f Change current half-byte to hexadecimal value |
| u Undo |
| q/^c/^d/<esc> Quit |
| */ |
| |
| #define FOR_hexedit |
| #include "toys.h" |
| |
| GLOBALS( |
| char *data; |
| long long len, base; |
| int numlen, undo, undolen; |
| unsigned height; |
| ) |
| |
| #define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1)) |
| |
| // Render all characters printable, using color to distinguish. |
| static int draw_char(FILE *fp, wchar_t broiled) |
| { |
| if (fp) { |
| if (broiled<32 || broiled>=127) { |
| if (broiled>127) { |
| tty_esc("2m"); |
| broiled &= 127; |
| } |
| if (broiled<32 || broiled==127) { |
| tty_esc("7m"); |
| if (broiled==127) broiled = 32; |
| else broiled += 64; |
| } |
| printf("%c", (int)broiled); |
| tty_esc("0m"); |
| } else printf("%c", (int)broiled); |
| } |
| |
| return 1; |
| } |
| |
| static void draw_tail(void) |
| { |
| tty_jump(0, TT.height); |
| tty_esc("K"); |
| |
| draw_trim(*toys.optargs, -1, 71); |
| } |
| |
| static void draw_line(long long yy) |
| { |
| int x, xx = 16; |
| |
| yy = (TT.base+yy)*16; |
| if (yy+xx>=TT.len) xx = TT.len-yy; |
| |
| if (yy<TT.len) { |
| printf("\r%0*llX ", TT.numlen, yy); |
| for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]); |
| printf("%*s", 2+3*(16-xx), ""); |
| for (x=0; x<xx; x++) draw_char(stdout, TT.data[yy+x]); |
| printf("%*s", 16-xx, ""); |
| } |
| tty_esc("K"); |
| } |
| |
| static void draw_page(void) |
| { |
| int y; |
| |
| tty_jump(0, 0); |
| for (y = 0; y<TT.height; y++) { |
| if (y) printf("\r\n"); |
| draw_line(y); |
| } |
| draw_tail(); |
| } |
| |
| // side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only |
| static void highlight(int xx, int yy, int side) |
| { |
| char cc = TT.data[16*(TT.base+yy)+xx]; |
| int i; |
| |
| // Display cursor |
| tty_jump(2+TT.numlen+3*xx, yy); |
| tty_esc("0m"); |
| if (side!=2) tty_esc("7m"); |
| if (side>1) printf("%02X", cc); |
| else for (i=0; i<2;) { |
| if (side==i) tty_esc("32m"); |
| printf("%X", (cc>>(4*(1&++i)))&15); |
| } |
| tty_esc("0m"); |
| tty_jump(TT.numlen+17*3+xx, yy); |
| draw_char(stdout, cc); |
| } |
| |
| void hexedit_main(void) |
| { |
| long long pos = 0, y; |
| int x, i, side = 0, key, ro = toys.optflags&FLAG_r, |
| fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR); |
| char keybuf[16]; |
| |
| *keybuf = 0; |
| |
| // Terminal setup |
| TT.height = 25; |
| terminal_size(0, &TT.height); |
| if (TT.height) TT.height--; |
| sigatexit(tty_sigreset); |
| tty_esc("0m"); |
| tty_esc("?25l"); |
| fflush(0); |
| xset_terminal(1, 1, 0); |
| |
| if ((TT.len = fdlength(fd))<1) error_exit("bad length"); |
| if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX; |
| // count file length hex in digits, rounded up to multiple of 4 |
| for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++); |
| TT.numlen += (4-TT.numlen)&3; |
| |
| TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0); |
| draw_page(); |
| |
| for (;;) { |
| // Scroll display if necessary |
| if (pos<0) pos = 0; |
| if (pos>=TT.len) pos = TT.len-1; |
| x = pos&15; |
| y = pos/16; |
| |
| i = 0; |
| while (y<TT.base) { |
| if (TT.base-y>(TT.height/2)) { |
| TT.base = y; |
| draw_page(); |
| } else { |
| TT.base--; |
| i++; |
| tty_esc("1T"); |
| tty_jump(0, 0); |
| draw_line(0); |
| } |
| } |
| while (y>=TT.base+TT.height) { |
| if (y-(TT.base+TT.height)>(TT.height/2)) { |
| TT.base = y-TT.height-1; |
| draw_page(); |
| } else { |
| TT.base++; |
| i++; |
| tty_esc("1S"); |
| tty_jump(0, TT.height-1); |
| draw_line(TT.height-1); |
| } |
| } |
| if (i) draw_tail(); |
| y -= TT.base; |
| |
| // Display cursor and flush output |
| highlight(x, y, ro ? 3 : side); |
| xflush(); |
| |
| // Wait for next key |
| key = scan_key(keybuf, -1); |
| // Exit for q, ctrl-c, ctrl-d, escape, or EOF |
| if (key==-1 || key==3 || key==4 || key==27 || key=='q') break; |
| highlight(x, y, 2); |
| |
| // Hex digit? |
| if (key>='a' && key<='f') key-=32; |
| if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) { |
| if (!side) { |
| long long *ll = (long long *)toybuf; |
| |
| ll[TT.undo] = pos; |
| toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos]; |
| if (TT.undolen < UNDO_LEN) TT.undolen++; |
| TT.undo %= UNDO_LEN; |
| } |
| |
| i = key - '0'; |
| if (i>9) i -= 7; |
| TT.data[pos] &= 15<<(4*side); |
| TT.data[pos] |= i<<(4*!side); |
| |
| if (++side==2) { |
| highlight(x, y, side); |
| side = 0; |
| ++pos; |
| } |
| } else side = 0; |
| if (key=='u') { |
| if (TT.undolen) { |
| long long *ll = (long long *)toybuf; |
| |
| TT.undolen--; |
| if (!TT.undo) TT.undo = UNDO_LEN; |
| pos = ll[--TT.undo]; |
| TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo]; |
| } |
| } |
| if (key>=256) { |
| key -= 256; |
| |
| if (key==KEY_UP) pos -= 16; |
| else if (key==KEY_DOWN) pos += 16; |
| else if (key==KEY_RIGHT) { |
| if (x<15) pos++; |
| } else if (key==KEY_LEFT) { |
| if (x) pos--; |
| } else if (key==KEY_PGUP) pos -= 16*TT.height; |
| else if (key==KEY_PGDN) pos += 16*TT.height; |
| else if (key==KEY_HOME) pos = 0; |
| else if (key==KEY_END) pos = TT.len-1; |
| } |
| } |
| munmap(TT.data, TT.len); |
| close(fd); |
| tty_reset(); |
| } |