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