1 /* hexedit.c - Hexadecimal file editor
2  *
3  * Copyright 2015 Rob Landley <rob@landley.net>
4  *
5  * No standard
6 
7 USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
8 
9 config 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 
GLOBALS(char * data;long long len,base;int numlen;unsigned height;)23 GLOBALS(
24   char *data;
25   long long len, base;
26   int numlen;
27   unsigned height;
28 )
29 
30 static void esc(char *s)
31 {
32   printf("\033[%s", s);
33 }
34 
jump(int x,int y)35 static 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 
fix_terminal(void)43 static 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 
sigttyreset(int i)52 static 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.
draw_char(char broiled)60 static 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 
draw_tail(void)77 static 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 
draw_line(long long yy)111 static 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 
draw_page(void)128 static 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
highlight(int xx,int yy,int side)141 static 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 
hexedit_main(void)170 void 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) {
251 down:
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