aboutsummaryrefslogtreecommitdiff
path: root/toys/other
diff options
context:
space:
mode:
Diffstat (limited to 'toys/other')
-rw-r--r--toys/other/hexedit.c250
1 files changed, 250 insertions, 0 deletions
diff --git a/toys/other/hexedit.c b/toys/other/hexedit.c
new file mode 100644
index 00000000..518755bb
--- /dev/null
+++ b/toys/other/hexedit.c
@@ -0,0 +1,250 @@
+/* hexedit.c - Hexadecimal file editor
+ *
+ * Copyright 2015 Rob Landley <rob@landley.net>
+ *
+ * No standard
+
+USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN))
+
+config HEXEDIT
+ bool "hexedit"
+ default y
+ help
+ usage: hexedit FILENAME
+
+ Hexadecimal file editor.
+
+ -r Read only (display but don't edit)
+*/
+
+#define FOR_hexedit
+#include "toys.h"
+
+GLOBALS(
+ char *data;
+ long long len, base;
+ int numlen;
+ unsigned height;
+)
+
+static void esc(char *s)
+{
+ printf("\033[%s", s);
+}
+
+static void jump(int x, int y)
+{
+ char s[32];
+
+ sprintf(s, "%d;%dH", y+1, x+1);
+ esc(s);
+}
+
+static void fix_terminal(void)
+{
+ set_terminal(1, 0, 0);
+ esc("?25h");
+ esc("0m");
+ jump(0, 999);
+}
+
+static void sigttyreset(int i)
+{
+ fix_terminal();
+ // how do I re-raise the signal so it dies with right signal info for wait()?
+ _exit(127);
+}
+
+// Render all characters printable, using color to distinguish.
+static void draw_char(char broiled)
+{
+ if (broiled<32 || broiled>=127) {
+ if (broiled>127) {
+ esc("2m");
+ broiled &= 127;
+ }
+ if (broiled<32 || broiled==127) {
+ esc("7m");
+ if (broiled==127) broiled = 32;
+ else broiled += 64;
+ }
+ printf("%c", broiled);
+ esc("0m");
+ } else printf("%c", broiled);
+}
+
+static void draw_line(long long yy)
+{
+ int x;
+
+ yy = (TT.base+yy)*16;
+
+ if (yy<TT.len) {
+ printf("\r%0*llX ", TT.numlen, yy);
+ for (x=0; x<16; x++) {
+ if (yy+x<TT.len) printf(" %02X", TT.data[yy+x]);
+ else printf(" ");
+ }
+ printf(" ");
+ for (x=0; x<16; x++) draw_char(TT.data[yy+x]);
+ }
+ esc("K");
+}
+
+static void draw_page(void)
+{
+ int y;
+
+ jump(0, 0);
+ for (y = 0; y<TT.height; y++) {
+ if (y) printf("\r\n");
+ draw_line(y);
+ }
+}
+
+// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
+static void highlight(int xx, int yy, int side)
+{
+ char cc = TT.data[16*(TT.base+yy)+xx];
+ int i;
+
+ // Display cursor
+ jump(2+TT.numlen+3*xx, yy);
+ esc("0m");
+ if (side!=2) esc("7m");
+ if (side>1) printf("%02X", cc);
+ else for (i=0; i<2;) {
+ if (side==i) esc("32m");
+ printf("%X", (cc>>(4*(1&++i)))&15);
+ }
+ esc("0m");
+ jump(TT.numlen+17*3+xx, yy);
+ draw_char(cc);
+}
+
+#define KEY_UP 256
+#define KEY_DOWN 257
+#define KEY_RIGHT 258
+#define KEY_LEFT 259
+#define KEY_PGUP 260
+#define KEY_PGDN 261
+#define KEY_HOME 262
+#define KEY_END 263
+#define KEY_INSERT 264
+
+void hexedit_main(void)
+{
+ // up down right left pgup pgdn home end ins
+ char *keys[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~",
+ "\033OH", "\033OF", "\033[2~", 0};
+ long long pos;
+ int x, y, i, side = 0, key, ro = toys.optflags&FLAG_r,
+ fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
+
+ TT.height = 25;
+ terminal_size(0, &TT.height);
+ if (TT.height) TT.height--;
+ sigatexit(sigttyreset);
+ esc("0m");
+ esc("?25l");
+ fflush(0);
+ set_terminal(1, 1, 0);
+
+ if ((TT.len = fdlength(fd))<0) error_exit("bad length");
+ if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
+ // count file length hex digits, rounded up to multiple of 4
+ for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
+ TT.numlen += (4-TT.numlen)&3;
+
+ TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
+
+ draw_page();
+
+ y = x = 0;
+ for (;;) {
+ // Get position within file, trimming if we overshot end.
+ pos = 16*(TT.base+y)+x;
+ if (pos>=TT.len) {
+ pos = TT.len-1;
+ x = (TT.len-1)%15;
+ }
+
+ // Display cursor
+ highlight(x, y, ro ? 3 : side);
+ fflush(0);
+
+ // Wait for next key
+ key = scan_key(toybuf, keys, 1);
+ // Exit for q, ctrl-c, ctrl-d, escape, or EOF
+ if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
+ highlight(x, y, 2);
+
+ if (key>='a' && key<='f') key-=32;
+ if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
+ i = key - '0';
+ if (i>9) i -= 7;
+ TT.data[pos] &= 15<<(4*side);
+ TT.data[pos] |= i<<(4*!side);
+
+ highlight(x, y, ++side);
+ if (side==2) {
+ side = 0;
+ if (++pos<TT.len && ++x==16) {
+ x = 0;
+ if (++y == TT.height) {
+ --y;
+ goto down;
+ }
+ }
+ }
+ }
+ if (key>255) side = 0;
+ if (key==KEY_UP) {
+ if (--y<0) {
+ if (TT.base) {
+ TT.base--;
+ esc("1T");
+ jump(0, TT.height);
+ esc("K");
+ jump(0, 0);
+ draw_line(0);
+ }
+ y = 0;
+ }
+ } else if (key==KEY_DOWN) {
+ if (y == TT.height-1 && pos+32<TT.len) {
+down:
+ TT.base++;
+ esc("1S");
+ jump(0, TT.height-1);
+ draw_line(TT.height-1);
+ }
+ if (pos+16<TT.len && ++y>=TT.height) y--;
+ } else if (key==KEY_RIGHT) {
+ if (x<15 && pos+1<TT.len) x++;
+ } else if (key==KEY_LEFT) {
+ if (x) x--;
+ } else if (key==KEY_PGUP) {
+ TT.base -= TT.height;
+ if (TT.base<0) TT.base = 0;
+ draw_page();
+ } else if (key==KEY_PGDN) {
+ TT.base += TT.height;
+ if ((TT.base*16)>=TT.len) TT.base=(TT.len-1)/16;
+ while ((TT.base+y)*16>=TT.len) y--;
+ if (16*(TT.base+y)+x>=TT.len) x = (TT.len-1)&15;
+ draw_page();
+ } else if (key==KEY_HOME) {
+ TT.base = 0;
+ x = 0;
+ draw_page();
+ } else if (key==KEY_END) {
+ TT.base=(TT.len-1)/16;
+ x = (TT.len-1)&15;
+ draw_page();
+ }
+ }
+ munmap(TT.data, TT.len);
+ close(fd);
+ fix_terminal();
+}