Home | History | Annotate | Download | only in other
      1 /* hexedit.c - Hexadecimal file editor
      2  *
      3  * Copyright 2015 Rob Landley <rob (at) 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 
     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", (int)broiled);
     54       tty_esc("0m");
     55     } else printf("%c", (int)broiled);
     56   }
     57 
     58   return 1;
     59 }
     60 
     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 
     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 
     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
     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 
    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 = 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();
    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