hexedit.c revision efb309d4cdb2f4c3926b0550d9dc1661c1e4a091
1/* hexedit.c - Hexadecimal file editor 2 * 3 * Copyright 2015 Rob Landley <rob@landley.net> 4 * 5 * No standard 6 7USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) 8 9config HEXEDIT 10 bool "hexedit" 11 default y 12 help 13 usage: hexedit FILENAME 14 15 Hexadecimal file editor. 16 17 -r Read only (display but don't edit) 18*/ 19 20#define FOR_hexedit 21#include "toys.h" 22 23GLOBALS( 24 char *data; 25 long long len, base; 26 int numlen, undo, undolen; 27 unsigned height; 28) 29 30#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1)) 31 32// Render all characters printable, using color to distinguish. 33static int draw_char(FILE *fp, wchar_t broiled) 34{ 35 if (fp) { 36 if (broiled<32 || broiled>=127) { 37 if (broiled>127) { 38 tty_esc("2m"); 39 broiled &= 127; 40 } 41 if (broiled<32 || broiled==127) { 42 tty_esc("7m"); 43 if (broiled==127) broiled = 32; 44 else broiled += 64; 45 } 46 printf("%c", broiled); 47 tty_esc("0m"); 48 } else printf("%c", broiled); 49 } 50 51 return 1; 52} 53 54static void draw_tail(void) 55{ 56 tty_jump(0, TT.height); 57 tty_esc("K"); 58 59 draw_rstr(*toys.optargs, 71, draw_char); 60} 61 62static void draw_line(long long yy) 63{ 64 int x, xx = 16; 65 66 yy = (TT.base+yy)*16; 67 if (yy+xx>=TT.len) xx = TT.len-yy; 68 69 if (yy<TT.len) { 70 printf("\r%0*llX ", TT.numlen, yy); 71 for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]); 72 printf("%*s", 2+3*(16-xx), ""); 73 for (x=0; x<xx; x++) draw_char(stdout, TT.data[yy+x]); 74 printf("%*s", 16-xx, ""); 75 } 76 tty_esc("K"); 77} 78 79static void draw_page(void) 80{ 81 int y; 82 83 tty_jump(0, 0); 84 for (y = 0; y<TT.height; y++) { 85 if (y) printf("\r\n"); 86 draw_line(y); 87 } 88 draw_tail(); 89} 90 91// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only 92static void highlight(int xx, int yy, int side) 93{ 94 char cc = TT.data[16*(TT.base+yy)+xx]; 95 int i; 96 97 // Display cursor 98 tty_jump(2+TT.numlen+3*xx, yy); 99 tty_esc("0m"); 100 if (side!=2) tty_esc("7m"); 101 if (side>1) printf("%02X", cc); 102 else for (i=0; i<2;) { 103 if (side==i) tty_esc("32m"); 104 printf("%X", (cc>>(4*(1&++i)))&15); 105 } 106 tty_esc("0m"); 107 tty_jump(TT.numlen+17*3+xx, yy); 108 draw_char(stdout, cc); 109} 110 111void hexedit_main(void) 112{ 113 long long pos = 0, y; 114 int x, i, side = 0, key, ro = toys.optflags&FLAG_r, 115 fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR); 116 char keybuf[16]; 117 118 *keybuf = 0; 119 120 // Terminal setup 121 TT.height = 25; 122 terminal_size(0, &TT.height); 123 if (TT.height) TT.height--; 124 sigatexit(tty_sigreset); 125 tty_esc("0m"); 126 tty_esc("?25l"); 127 fflush(0); 128 set_terminal(1, 1, 0); 129 130 if ((TT.len = fdlength(fd))<0) error_exit("bad length"); 131 if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX; 132 // count file length hex in digits, rounded up to multiple of 4 133 for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++); 134 TT.numlen += (4-TT.numlen)&3; 135 136 TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0); 137 draw_page(); 138 139 for (;;) { 140 // Scroll display if necessary 141 if (pos<0) pos = 0; 142 if (pos>TT.len) pos = TT.len-1; 143 x = pos&15; 144 y = pos/16; 145 146 i = 0; 147 while (y<TT.base) { 148 if (TT.base-y>(TT.height/2)) { 149 TT.base = y; 150 draw_page(); 151 } else { 152 TT.base--; 153 i++; 154 tty_esc("1T"); 155 tty_jump(0, 0); 156 draw_line(0); 157 } 158 } 159 while (y>=TT.base+TT.height) { 160 if (y-(TT.base+TT.height)>(TT.height/2)) { 161 TT.base = y-TT.height-1; 162 draw_page(); 163 } else { 164 TT.base++; 165 i++; 166 tty_esc("1S"); 167 tty_jump(0, TT.height-1); 168 draw_line(TT.height-1); 169 } 170 } 171 if (i) draw_tail(); 172 y -= TT.base; 173 174 // Display cursor and flush output 175 highlight(x, y, ro ? 3 : side); 176 xflush(); 177 178 // Wait for next key 179 key = scan_key(keybuf, -1); 180 // Exit for q, ctrl-c, ctrl-d, escape, or EOF 181 if (key==-1 || key==3 || key==4 || key==27 || key=='q') break; 182 highlight(x, y, 2); 183 184 // Hex digit? 185 if (key>='a' && key<='f') key-=32; 186 if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) { 187 if (!side) { 188 long long *ll = (long long *)toybuf; 189 190 ll[TT.undo] = pos; 191 toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos]; 192 if (TT.undolen < UNDO_LEN) TT.undolen++; 193 TT.undo %= UNDO_LEN; 194 } 195 196 i = key - '0'; 197 if (i>9) i -= 7; 198 TT.data[pos] &= 15<<(4*side); 199 TT.data[pos] |= i<<(4*!side); 200 201 if (++side==2) { 202 highlight(x, y, side); 203 side = 0; 204 ++pos; 205 } 206 } else side = 0; 207 if (key=='u') { 208 if (TT.undolen) { 209 long long *ll = (long long *)toybuf; 210 211 TT.undolen--; 212 if (!TT.undo) TT.undo = UNDO_LEN; 213 pos = ll[--TT.undo]; 214 TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo]; 215 } 216 } 217 if (key>256) { 218 key -= 256; 219 220 if (key==KEY_UP) pos -= 16; 221 else if (key==KEY_DOWN) pos += 16; 222 else if (key==KEY_RIGHT) { 223 if (x<15) pos++; 224 } else if (key==KEY_LEFT) { 225 if (x) pos--; 226 } else if (key==KEY_PGUP) pos -= 16*TT.height; 227 else if (key==KEY_PGDN) pos += 16*TT.height; 228 else if (key==KEY_HOME) pos = 0; 229 else if (key==KEY_END) pos = TT.len-1; 230 } 231 } 232 munmap(TT.data, TT.len); 233 close(fd); 234 tty_reset(); 235} 236