toybox/toys/other/hexedit.c
<<
>>
Prefs
   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. 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
  30GLOBALS(
  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.
  40static 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", (int)broiled);
  54      tty_esc("0m");
  55    } else printf("%c", (int)broiled);
  56  }
  57
  58  return 1;
  59}
  60
  61static 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
  69static 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
  86static 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
  99static 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
 118void 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, 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 = xmmap(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(1);
 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