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. 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 
GLOBALS(char * data;long long len,base;int numlen,undo,undolen;unsigned height;)30 GLOBALS(
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.
40 static 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 
draw_tail(void)61 static 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 
draw_line(long long yy)69 static 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 
draw_page(void)86 static 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
highlight(int xx,int yy,int side)99 static 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 
hexedit_main(void)118 void 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