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