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