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; 27 unsigned height; 28) 29 30static void esc(char *s) 31{ 32 printf("\033[%s", s); 33} 34 35static void jump(int x, int y) 36{ 37 char s[32]; 38 39 sprintf(s, "%d;%dH", y+1, x+1); 40 esc(s); 41} 42 43static void fix_terminal(void) 44{ 45 set_terminal(1, 0, 0); 46 esc("?25h"); 47 esc("0m"); 48 jump(0, 999); 49 esc("K"); 50} 51 52static void sigttyreset(int i) 53{ 54 fix_terminal(); 55 // how do I re-raise the signal so it dies with right signal info for wait()? 56 _exit(127); 57} 58 59// Render all characters printable, using color to distinguish. 60static void draw_char(char broiled) 61{ 62 if (broiled<32 || broiled>=127) { 63 if (broiled>127) { 64 esc("2m"); 65 broiled &= 127; 66 } 67 if (broiled<32 || broiled==127) { 68 esc("7m"); 69 if (broiled==127) broiled = 32; 70 else broiled += 64; 71 } 72 printf("%c", broiled); 73 esc("0m"); 74 } else printf("%c", broiled); 75} 76 77static void draw_tail(void) 78{ 79 int i = 0, width = 0, w, len; 80 char *start = *toys.optargs, *end; 81 82 jump(0, TT.height); 83 esc("K"); 84 85 // First time, make sure we fit in 71 chars (advancing start as necessary). 86 // Second time, print from start to end, escaping nonprintable chars. 87 for (i=0; i<2; i++) { 88 for (end = start; *end;) { 89 wchar_t wc; 90 91 len = mbrtowc(&wc, end, 99, 0); 92 if (len<0 || wc<32 || (w = wcwidth(wc))<0) { 93 len = w = 1; 94 if (i) draw_char(*end); 95 } else if (i) fwrite(end, len, 1, stdout); 96 end += len; 97 98 if (!i) { 99 width += w; 100 while (width > 71) { 101 len = mbrtowc(&wc, start, 99, 0); 102 if (len<0 || wc<32 || (w = wcwidth(wc))<0) len = w = 1; 103 width -= w; 104 start += len; 105 } 106 } 107 } 108 } 109} 110 111static void draw_line(long long yy) 112{ 113 int x, xx = 16; 114 115 yy = (TT.base+yy)*16; 116 if (yy+xx>=TT.len) xx = TT.len-yy; 117 118 if (yy<TT.len) { 119 printf("\r%0*llX ", TT.numlen, yy); 120 for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]); 121 printf("%*s", 2+3*(16-xx), ""); 122 for (x=0; x<xx; x++) draw_char(TT.data[yy+x]); 123 printf("%*s", 16-xx, ""); 124 } 125 esc("K"); 126} 127 128static void draw_page(void) 129{ 130 int y; 131 132 jump(0, 0); 133 for (y = 0; y<TT.height; y++) { 134 if (y) printf("\r\n"); 135 draw_line(y); 136 } 137 draw_tail(); 138} 139 140// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only 141static void highlight(int xx, int yy, int side) 142{ 143 char cc = TT.data[16*(TT.base+yy)+xx]; 144 int i; 145 146 // Display cursor 147 jump(2+TT.numlen+3*xx, yy); 148 esc("0m"); 149 if (side!=2) esc("7m"); 150 if (side>1) printf("%02X", cc); 151 else for (i=0; i<2;) { 152 if (side==i) esc("32m"); 153 printf("%X", (cc>>(4*(1&++i)))&15); 154 } 155 esc("0m"); 156 jump(TT.numlen+17*3+xx, yy); 157 draw_char(cc); 158} 159 160#define KEY_UP 256 161#define KEY_DOWN 257 162#define KEY_RIGHT 258 163#define KEY_LEFT 259 164#define KEY_PGUP 260 165#define KEY_PGDN 261 166#define KEY_HOME 262 167#define KEY_END 263 168#define KEY_INSERT 264 169 170void hexedit_main(void) 171{ 172 // up down right left pgup pgdn home end ins 173 char *keys[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~", 174 "\033OH", "\033OF", "\033[2~", 0}; 175 long long pos; 176 int x, y, i, side = 0, key, ro = toys.optflags&FLAG_r, 177 fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR); 178 179 TT.height = 25; 180 terminal_size(0, &TT.height); 181 if (TT.height) TT.height--; 182 sigatexit(sigttyreset); 183 esc("0m"); 184 esc("?25l"); 185 fflush(0); 186 set_terminal(1, 1, 0); 187 188 if ((TT.len = fdlength(fd))<0) error_exit("bad length"); 189 if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX; 190 // count file length hex digits, rounded up to multiple of 4 191 for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++); 192 TT.numlen += (4-TT.numlen)&3; 193 194 TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0); 195 196 draw_page(); 197 198 y = x = 0; 199 for (;;) { 200 // Get position within file, trimming if we overshot end. 201 pos = 16*(TT.base+y)+x; 202 if (pos>=TT.len) { 203 pos = TT.len-1; 204 x = pos&15; 205 y = (pos/16)-TT.base; 206 } 207 208 // Display cursor 209 highlight(x, y, ro ? 3 : side); 210 xprintf(""); 211 212 // Wait for next key 213 key = scan_key(toybuf, keys, 1); 214 // Exit for q, ctrl-c, ctrl-d, escape, or EOF 215 if (key==-1 || key==3 || key==4 || key==27 || key=='q') break; 216 highlight(x, y, 2); 217 218 if (key>='a' && key<='f') key-=32; 219 if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) { 220 i = key - '0'; 221 if (i>9) i -= 7; 222 TT.data[pos] &= 15<<(4*side); 223 TT.data[pos] |= i<<(4*!side); 224 225 highlight(x, y, ++side); 226 if (side==2) { 227 side = 0; 228 if (++pos<TT.len && ++x==16) { 229 x = 0; 230 if (++y == TT.height) { 231 --y; 232 goto down; 233 } 234 } 235 } 236 } 237 if (key>255) side = 0; 238 if (key==KEY_UP) { 239 if (--y<0) { 240 if (TT.base) { 241 TT.base--; 242 esc("1T"); 243 draw_tail(); 244 jump(0, 0); 245 draw_line(0); 246 } 247 y = 0; 248 } 249 } else if (key==KEY_DOWN) { 250 if (y == TT.height-1 && (pos|15)+1<TT.len) { 251down: 252 TT.base++; 253 esc("1S"); 254 jump(0, TT.height-1); 255 draw_line(TT.height-1); 256 draw_tail(); 257 } 258 if (++y>=TT.height) y--; 259 } else if (key==KEY_RIGHT) { 260 if (x<15 && pos+1<TT.len) x++; 261 } else if (key==KEY_LEFT) { 262 if (x) x--; 263 } else if (key==KEY_PGUP) { 264 TT.base -= TT.height; 265 if (TT.base<0) TT.base = 0; 266 draw_page(); 267 } else if (key==KEY_PGDN) { 268 TT.base += TT.height; 269 if ((TT.base*16)>=TT.len) TT.base=(TT.len-1)/16; 270 while ((TT.base+y)*16>=TT.len) y--; 271 if (16*(TT.base+y)+x>=TT.len) x = (TT.len-1)&15; 272 draw_page(); 273 } else if (key==KEY_HOME) { 274 TT.base = 0; 275 x = 0; 276 draw_page(); 277 } else if (key==KEY_END) { 278 TT.base=(TT.len-1)/16; 279 x = (TT.len-1)&15; 280 draw_page(); 281 } 282 } 283 munmap(TT.data, TT.len); 284 close(fd); 285 fix_terminal(); 286} 287