10f66f451Sopenharmony_ci/* hexedit.c - Hexadecimal file editor
20f66f451Sopenharmony_ci *
30f66f451Sopenharmony_ci * Copyright 2015 Rob Landley <rob@landley.net>
40f66f451Sopenharmony_ci *
50f66f451Sopenharmony_ci * No standard
60f66f451Sopenharmony_ci
70f66f451Sopenharmony_ciUSE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
80f66f451Sopenharmony_ci
90f66f451Sopenharmony_ciconfig HEXEDIT
100f66f451Sopenharmony_ci  bool "hexedit"
110f66f451Sopenharmony_ci  default y
120f66f451Sopenharmony_ci  help
130f66f451Sopenharmony_ci    usage: hexedit FILENAME
140f66f451Sopenharmony_ci
150f66f451Sopenharmony_ci    Hexadecimal file editor. All changes are written to disk immediately.
160f66f451Sopenharmony_ci
170f66f451Sopenharmony_ci    -r	Read only (display but don't edit)
180f66f451Sopenharmony_ci
190f66f451Sopenharmony_ci    Keys:
200f66f451Sopenharmony_ci    Arrows        Move left/right/up/down by one line/column
210f66f451Sopenharmony_ci    Pg Up/Pg Dn   Move up/down by one page
220f66f451Sopenharmony_ci    0-9, a-f      Change current half-byte to hexadecimal value
230f66f451Sopenharmony_ci    u             Undo
240f66f451Sopenharmony_ci    q/^c/^d/<esc> Quit
250f66f451Sopenharmony_ci*/
260f66f451Sopenharmony_ci
270f66f451Sopenharmony_ci#define FOR_hexedit
280f66f451Sopenharmony_ci#include "toys.h"
290f66f451Sopenharmony_ci
300f66f451Sopenharmony_ciGLOBALS(
310f66f451Sopenharmony_ci  char *data;
320f66f451Sopenharmony_ci  long long len, base;
330f66f451Sopenharmony_ci  int numlen, undo, undolen;
340f66f451Sopenharmony_ci  unsigned height;
350f66f451Sopenharmony_ci)
360f66f451Sopenharmony_ci
370f66f451Sopenharmony_ci#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
380f66f451Sopenharmony_ci
390f66f451Sopenharmony_ci// Render all characters printable, using color to distinguish.
400f66f451Sopenharmony_cistatic int draw_char(FILE *fp, wchar_t broiled)
410f66f451Sopenharmony_ci{
420f66f451Sopenharmony_ci  if (fp) {
430f66f451Sopenharmony_ci    if (broiled<32 || broiled>=127) {
440f66f451Sopenharmony_ci      if (broiled>127) {
450f66f451Sopenharmony_ci        tty_esc("2m");
460f66f451Sopenharmony_ci        broiled &= 127;
470f66f451Sopenharmony_ci      }
480f66f451Sopenharmony_ci      if (broiled<32 || broiled==127) {
490f66f451Sopenharmony_ci        tty_esc("7m");
500f66f451Sopenharmony_ci        if (broiled==127) broiled = 32;
510f66f451Sopenharmony_ci        else broiled += 64;
520f66f451Sopenharmony_ci      }
530f66f451Sopenharmony_ci      printf("%c", (int)broiled);
540f66f451Sopenharmony_ci      tty_esc("0m");
550f66f451Sopenharmony_ci    } else printf("%c", (int)broiled);
560f66f451Sopenharmony_ci  }
570f66f451Sopenharmony_ci
580f66f451Sopenharmony_ci  return 1;
590f66f451Sopenharmony_ci}
600f66f451Sopenharmony_ci
610f66f451Sopenharmony_cistatic void draw_tail(void)
620f66f451Sopenharmony_ci{
630f66f451Sopenharmony_ci  tty_jump(0, TT.height);
640f66f451Sopenharmony_ci  tty_esc("K");
650f66f451Sopenharmony_ci
660f66f451Sopenharmony_ci  draw_trim(*toys.optargs, -1, 71);
670f66f451Sopenharmony_ci}
680f66f451Sopenharmony_ci
690f66f451Sopenharmony_cistatic void draw_line(long long yy)
700f66f451Sopenharmony_ci{
710f66f451Sopenharmony_ci  int x, xx = 16;
720f66f451Sopenharmony_ci
730f66f451Sopenharmony_ci  yy = (TT.base+yy)*16;
740f66f451Sopenharmony_ci  if (yy+xx>=TT.len) xx = TT.len-yy;
750f66f451Sopenharmony_ci
760f66f451Sopenharmony_ci  if (yy<TT.len) {
770f66f451Sopenharmony_ci    printf("\r%0*llX ", TT.numlen, yy);
780f66f451Sopenharmony_ci    for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]);
790f66f451Sopenharmony_ci    printf("%*s", 2+3*(16-xx), "");
800f66f451Sopenharmony_ci    for (x=0; x<xx; x++) draw_char(stdout, TT.data[yy+x]);
810f66f451Sopenharmony_ci    printf("%*s", 16-xx, "");
820f66f451Sopenharmony_ci  }
830f66f451Sopenharmony_ci  tty_esc("K");
840f66f451Sopenharmony_ci}
850f66f451Sopenharmony_ci
860f66f451Sopenharmony_cistatic void draw_page(void)
870f66f451Sopenharmony_ci{
880f66f451Sopenharmony_ci  int y;
890f66f451Sopenharmony_ci
900f66f451Sopenharmony_ci  tty_jump(0, 0);
910f66f451Sopenharmony_ci  for (y = 0; y<TT.height; y++) {
920f66f451Sopenharmony_ci    if (y) printf("\r\n");
930f66f451Sopenharmony_ci    draw_line(y);
940f66f451Sopenharmony_ci  }
950f66f451Sopenharmony_ci  draw_tail();
960f66f451Sopenharmony_ci}
970f66f451Sopenharmony_ci
980f66f451Sopenharmony_ci// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
990f66f451Sopenharmony_cistatic void highlight(int xx, int yy, int side)
1000f66f451Sopenharmony_ci{
1010f66f451Sopenharmony_ci  char cc = TT.data[16*(TT.base+yy)+xx];
1020f66f451Sopenharmony_ci  int i;
1030f66f451Sopenharmony_ci
1040f66f451Sopenharmony_ci  // Display cursor
1050f66f451Sopenharmony_ci  tty_jump(2+TT.numlen+3*xx, yy);
1060f66f451Sopenharmony_ci  tty_esc("0m");
1070f66f451Sopenharmony_ci  if (side!=2) tty_esc("7m");
1080f66f451Sopenharmony_ci  if (side>1) printf("%02X", cc);
1090f66f451Sopenharmony_ci  else for (i=0; i<2;) {
1100f66f451Sopenharmony_ci    if (side==i) tty_esc("32m");
1110f66f451Sopenharmony_ci    printf("%X", (cc>>(4*(1&++i)))&15);
1120f66f451Sopenharmony_ci  }
1130f66f451Sopenharmony_ci  tty_esc("0m");
1140f66f451Sopenharmony_ci  tty_jump(TT.numlen+17*3+xx, yy);
1150f66f451Sopenharmony_ci  draw_char(stdout, cc);
1160f66f451Sopenharmony_ci}
1170f66f451Sopenharmony_ci
1180f66f451Sopenharmony_civoid hexedit_main(void)
1190f66f451Sopenharmony_ci{
1200f66f451Sopenharmony_ci  long long pos = 0, y;
1210f66f451Sopenharmony_ci  int x, i, side = 0, key, ro = toys.optflags&FLAG_r,
1220f66f451Sopenharmony_ci      fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
1230f66f451Sopenharmony_ci  char keybuf[16];
1240f66f451Sopenharmony_ci
1250f66f451Sopenharmony_ci  *keybuf = 0;
1260f66f451Sopenharmony_ci
1270f66f451Sopenharmony_ci  // Terminal setup
1280f66f451Sopenharmony_ci  TT.height = 25;
1290f66f451Sopenharmony_ci  terminal_size(0, &TT.height);
1300f66f451Sopenharmony_ci  if (TT.height) TT.height--;
1310f66f451Sopenharmony_ci  sigatexit(tty_sigreset);
1320f66f451Sopenharmony_ci  tty_esc("0m");
1330f66f451Sopenharmony_ci  tty_esc("?25l");
1340f66f451Sopenharmony_ci  fflush(0);
1350f66f451Sopenharmony_ci  xset_terminal(1, 1, 0, 0);
1360f66f451Sopenharmony_ci
1370f66f451Sopenharmony_ci  if ((TT.len = fdlength(fd))<1) error_exit("bad length");
1380f66f451Sopenharmony_ci  if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
1390f66f451Sopenharmony_ci  // count file length hex in digits, rounded up to multiple of 4
1400f66f451Sopenharmony_ci  for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
1410f66f451Sopenharmony_ci  TT.numlen += (4-TT.numlen)&3;
1420f66f451Sopenharmony_ci
1430f66f451Sopenharmony_ci  TT.data = xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
1440f66f451Sopenharmony_ci  draw_page();
1450f66f451Sopenharmony_ci
1460f66f451Sopenharmony_ci  for (;;) {
1470f66f451Sopenharmony_ci    // Scroll display if necessary
1480f66f451Sopenharmony_ci    if (pos<0) pos = 0;
1490f66f451Sopenharmony_ci    if (pos>=TT.len) pos = TT.len-1;
1500f66f451Sopenharmony_ci    x = pos&15;
1510f66f451Sopenharmony_ci    y = pos/16;
1520f66f451Sopenharmony_ci
1530f66f451Sopenharmony_ci    i = 0;
1540f66f451Sopenharmony_ci    while (y<TT.base) {
1550f66f451Sopenharmony_ci      if (TT.base-y>(TT.height/2)) {
1560f66f451Sopenharmony_ci        TT.base = y;
1570f66f451Sopenharmony_ci        draw_page();
1580f66f451Sopenharmony_ci      } else {
1590f66f451Sopenharmony_ci        TT.base--;
1600f66f451Sopenharmony_ci        i++;
1610f66f451Sopenharmony_ci        tty_jump(0, 0);
1620f66f451Sopenharmony_ci        tty_esc("1L");
1630f66f451Sopenharmony_ci        draw_line(0);
1640f66f451Sopenharmony_ci      }
1650f66f451Sopenharmony_ci    }
1660f66f451Sopenharmony_ci    while (y>=TT.base+TT.height) {
1670f66f451Sopenharmony_ci      if (y-(TT.base+TT.height)>(TT.height/2)) {
1680f66f451Sopenharmony_ci        TT.base = y-TT.height-1;
1690f66f451Sopenharmony_ci        draw_page();
1700f66f451Sopenharmony_ci      } else {
1710f66f451Sopenharmony_ci        TT.base++;
1720f66f451Sopenharmony_ci        i++;
1730f66f451Sopenharmony_ci        tty_jump(0, 0);
1740f66f451Sopenharmony_ci        tty_esc("1M");
1750f66f451Sopenharmony_ci        tty_jump(0, TT.height-1);
1760f66f451Sopenharmony_ci        draw_line(TT.height-1);
1770f66f451Sopenharmony_ci      }
1780f66f451Sopenharmony_ci    }
1790f66f451Sopenharmony_ci    if (i) draw_tail();
1800f66f451Sopenharmony_ci    y -= TT.base;
1810f66f451Sopenharmony_ci
1820f66f451Sopenharmony_ci    // Display cursor and flush output
1830f66f451Sopenharmony_ci    highlight(x, y, ro ? 3 : side);
1840f66f451Sopenharmony_ci    xflush(1);
1850f66f451Sopenharmony_ci
1860f66f451Sopenharmony_ci    // Wait for next key
1870f66f451Sopenharmony_ci    key = scan_key(keybuf, -1);
1880f66f451Sopenharmony_ci    // Exit for q, ctrl-c, ctrl-d, escape, or EOF
1890f66f451Sopenharmony_ci    if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
1900f66f451Sopenharmony_ci    highlight(x, y, 2);
1910f66f451Sopenharmony_ci
1920f66f451Sopenharmony_ci    // Hex digit?
1930f66f451Sopenharmony_ci    if (key>='a' && key<='f') key-=32;
1940f66f451Sopenharmony_ci    if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
1950f66f451Sopenharmony_ci      if (!side) {
1960f66f451Sopenharmony_ci        long long *ll = (long long *)toybuf;
1970f66f451Sopenharmony_ci
1980f66f451Sopenharmony_ci        ll[TT.undo] = pos;
1990f66f451Sopenharmony_ci        toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos];
2000f66f451Sopenharmony_ci        if (TT.undolen < UNDO_LEN) TT.undolen++;
2010f66f451Sopenharmony_ci        TT.undo %= UNDO_LEN;
2020f66f451Sopenharmony_ci      }
2030f66f451Sopenharmony_ci
2040f66f451Sopenharmony_ci      i = key - '0';
2050f66f451Sopenharmony_ci      if (i>9) i -= 7;
2060f66f451Sopenharmony_ci      TT.data[pos] &= 15<<(4*side);
2070f66f451Sopenharmony_ci      TT.data[pos] |= i<<(4*!side);
2080f66f451Sopenharmony_ci
2090f66f451Sopenharmony_ci      if (++side==2) {
2100f66f451Sopenharmony_ci        highlight(x, y, side);
2110f66f451Sopenharmony_ci        side = 0;
2120f66f451Sopenharmony_ci        ++pos;
2130f66f451Sopenharmony_ci      }
2140f66f451Sopenharmony_ci    } else side = 0;
2150f66f451Sopenharmony_ci    if (key=='u') {
2160f66f451Sopenharmony_ci      if (TT.undolen) {
2170f66f451Sopenharmony_ci        long long *ll = (long long *)toybuf;
2180f66f451Sopenharmony_ci
2190f66f451Sopenharmony_ci        TT.undolen--;
2200f66f451Sopenharmony_ci        if (!TT.undo) TT.undo = UNDO_LEN;
2210f66f451Sopenharmony_ci        pos = ll[--TT.undo];
2220f66f451Sopenharmony_ci        TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
2230f66f451Sopenharmony_ci      }
2240f66f451Sopenharmony_ci    }
2250f66f451Sopenharmony_ci    if (key>=256) {
2260f66f451Sopenharmony_ci      key -= 256;
2270f66f451Sopenharmony_ci
2280f66f451Sopenharmony_ci      if (key==KEY_UP) pos -= 16;
2290f66f451Sopenharmony_ci      else if (key==KEY_DOWN) pos += 16;
2300f66f451Sopenharmony_ci      else if (key==KEY_RIGHT) {
2310f66f451Sopenharmony_ci        if (x<15) pos++;
2320f66f451Sopenharmony_ci      } else if (key==KEY_LEFT) {
2330f66f451Sopenharmony_ci        if (x) pos--;
2340f66f451Sopenharmony_ci      } else if (key==KEY_PGUP) pos -= 16*TT.height;
2350f66f451Sopenharmony_ci      else if (key==KEY_PGDN) pos += 16*TT.height;
2360f66f451Sopenharmony_ci      else if (key==KEY_HOME) pos = 0;
2370f66f451Sopenharmony_ci      else if (key==KEY_END) pos = TT.len-1;
2380f66f451Sopenharmony_ci    }
2390f66f451Sopenharmony_ci  }
2400f66f451Sopenharmony_ci  munmap(TT.data, TT.len);
2410f66f451Sopenharmony_ci  close(fd);
2420f66f451Sopenharmony_ci  tty_reset();
2430f66f451Sopenharmony_ci}
244