You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
8.8 KiB
365 lines
8.8 KiB
/* hexedit.c - Hexadecimal file editor
|
|
*
|
|
* Copyright 2015 Rob Landley <rob@landley.net>
|
|
*
|
|
* No standard
|
|
|
|
USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
|
|
|
|
config HEXEDIT
|
|
bool "hexedit"
|
|
default y
|
|
help
|
|
usage: hexedit FILE
|
|
|
|
Hexadecimal file editor/viewer. All changes are written to disk immediately.
|
|
|
|
-r Read only (display but don't edit)
|
|
|
|
Keys:
|
|
Arrows Move left/right/up/down by one line/column
|
|
PgUp/PgDn Move up/down by one page
|
|
Home/End Start/end of line (start/end of file with ctrl)
|
|
0-9, a-f Change current half-byte to hexadecimal value
|
|
^J or : Jump (+/- for relative offset, otherwise absolute address)
|
|
^F or / Find string (^G/n: next, ^D/p: previous match)
|
|
u Undo
|
|
q/^C/^Q/Esc Quit
|
|
*/
|
|
|
|
#define FOR_hexedit
|
|
#include "toys.h"
|
|
|
|
GLOBALS(
|
|
char *data;
|
|
long long len, base;
|
|
int numlen, undo, undolen;
|
|
unsigned rows, cols;
|
|
long long pos;
|
|
char keybuf[16];
|
|
char input[80];
|
|
char *search;
|
|
)
|
|
|
|
#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
|
|
|
|
static void show_error(char *what)
|
|
{
|
|
tty_jump(0, TT.rows);
|
|
printf("\e[41m\e[37m\e[K\e[1m%s\e[0m", what);
|
|
xflush(1);
|
|
msleep(500);
|
|
}
|
|
|
|
// TODO: support arrow keys, insertion, and scrolling (and reuse in vi)
|
|
static int prompt(char *prompt, char *initial_value)
|
|
{
|
|
int yes = 0, key, len = strlen(initial_value);
|
|
|
|
strcpy(TT.input, initial_value);
|
|
while (1) {
|
|
tty_jump(0, TT.rows);
|
|
tty_esc("K");
|
|
printf("\e[1m%s: \e[0m%s", prompt, TT.input);
|
|
tty_esc("?25h");
|
|
xflush(1);
|
|
|
|
key = scan_key(TT.keybuf, -1);
|
|
if (key < 0 || key == 27) break;
|
|
if (key == '\r') {
|
|
yes = len; // Hitting enter with no input counts as cancellation.
|
|
break;
|
|
}
|
|
|
|
if (key == 0x7f) {
|
|
if (len > 0) TT.input[--len] = 0;
|
|
} else if (key == 'U'-'@') {
|
|
while (len > 0) TT.input[--len] = 0;
|
|
} else if (key >= ' ' && key < 0x7f && len < sizeof(TT.input)) {
|
|
TT.input[len++] = key;
|
|
}
|
|
}
|
|
|
|
tty_esc("?25l");
|
|
return yes;
|
|
}
|
|
|
|
// Render all characters printable, using color to distinguish.
|
|
static void draw_char(int ch)
|
|
{
|
|
if (ch >= ' ' && ch < 0x7f) putchar(ch);
|
|
else {
|
|
if (ch < ' ') printf("\e[31m%c", ch + '@');
|
|
else printf("\e[35m?");
|
|
}
|
|
printf("\e[0m");
|
|
}
|
|
|
|
static void draw_status(void)
|
|
{
|
|
char line[80];
|
|
|
|
tty_jump(0, TT.rows);
|
|
tty_esc("K");
|
|
|
|
snprintf(line, sizeof(line), "\"%s\"%s, %#llx/%#llx", *toys.optargs,
|
|
FLAG(r) ? " [readonly]" : "", TT.pos, TT.len);
|
|
draw_trim(line, -1, TT.cols);
|
|
}
|
|
|
|
static void draw_byte(int byte)
|
|
{
|
|
if (byte) printf("%02x", byte);
|
|
else printf("\e[2m00\e[0m");
|
|
}
|
|
|
|
static void draw_line(long long yy)
|
|
{
|
|
int x, xx = 16;
|
|
|
|
yy = (TT.base+yy)*16;
|
|
if (yy+xx>=TT.len) xx = TT.len-yy;
|
|
|
|
if (yy<TT.len) {
|
|
printf("\r\e[33m%0*llx\e[0m ", TT.numlen, yy);
|
|
for (x=0; x<xx; x++) {
|
|
putchar(' ');
|
|
draw_byte(TT.data[yy+x]);
|
|
}
|
|
printf("%*s", 2+3*(16-xx), "");
|
|
for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
|
|
printf("%*s", 16-xx, "");
|
|
}
|
|
tty_esc("K");
|
|
}
|
|
|
|
static void draw_page(void)
|
|
{
|
|
int y;
|
|
|
|
tty_jump(0, 0);
|
|
for (y = 0; y<TT.rows; y++) {
|
|
if (y) printf("\r\n");
|
|
draw_line(y);
|
|
}
|
|
draw_status();
|
|
}
|
|
|
|
// 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 in hex area.
|
|
tty_jump(2+TT.numlen+3*xx, yy);
|
|
tty_esc("0m");
|
|
if (side!=2) tty_esc("7m");
|
|
if (side>1) draw_byte(cc);
|
|
else for (i=0; i<2;) {
|
|
if (side==i) tty_esc("32m");
|
|
printf("%x", (cc>>(4*(1&++i)))&15);
|
|
}
|
|
tty_jump(TT.numlen+17*3+xx, yy);
|
|
|
|
// Display cursor in text area.
|
|
if (side!=2) tty_esc("7m");
|
|
draw_char(cc);
|
|
}
|
|
|
|
static void find_next(int pos)
|
|
{
|
|
char *p;
|
|
|
|
p = memmem(TT.data+pos, TT.len-pos, TT.search, strlen(TT.search));
|
|
if (p) TT.pos = p - TT.data;
|
|
else show_error("No match!");
|
|
}
|
|
|
|
static void find_prev(int pos)
|
|
{
|
|
size_t len = strlen(TT.search);
|
|
|
|
for (; pos >= 0; pos--) {
|
|
if (!memcmp(TT.data+pos, TT.search, len)) {
|
|
TT.pos = pos;
|
|
return;
|
|
}
|
|
}
|
|
show_error("No match!");
|
|
}
|
|
|
|
void hexedit_main(void)
|
|
{
|
|
long long y;
|
|
int x, i, side = 0, key, fd;
|
|
|
|
// Terminal setup
|
|
TT.cols = 80;
|
|
TT.rows = 24;
|
|
terminal_size(&TT.cols, &TT.rows);
|
|
if (TT.rows) TT.rows--;
|
|
xsignal(SIGWINCH, generic_signal);
|
|
sigatexit(tty_sigreset);
|
|
tty_esc("0m");
|
|
tty_esc("?25l");
|
|
xflush(1);
|
|
xset_terminal(1, 1, 0, 0);
|
|
|
|
if (access(*toys.optargs, W_OK)) toys.optflags |= FLAG_r;
|
|
fd = xopen(*toys.optargs, FLAG(r) ? O_RDONLY : O_RDWR);
|
|
if ((TT.len = fdlength(fd))<1) error_exit("bad length");
|
|
if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
|
|
// count file length hex in digits, rounded up to multiple of 4
|
|
for (TT.pos = TT.len, TT.numlen = 0; TT.pos; TT.pos >>= 4, TT.numlen++);
|
|
TT.numlen += (4-TT.numlen)&3;
|
|
|
|
TT.data=xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!FLAG(r)), MAP_SHARED, fd, 0);
|
|
close(fd);
|
|
draw_page();
|
|
|
|
for (;;) {
|
|
// Scroll display if necessary
|
|
if (TT.pos<0) TT.pos = 0;
|
|
if (TT.pos>=TT.len) TT.pos = TT.len-1;
|
|
x = TT.pos&15;
|
|
y = TT.pos/16;
|
|
|
|
while (y<TT.base) {
|
|
if (TT.base-y>(TT.rows/2)) {
|
|
TT.base = y;
|
|
draw_page();
|
|
} else {
|
|
TT.base--;
|
|
tty_jump(0, 0);
|
|
tty_esc("1L");
|
|
draw_line(0);
|
|
}
|
|
}
|
|
while (y>=TT.base+TT.rows) {
|
|
if (y-(TT.base+TT.rows)>(TT.rows/2)) {
|
|
TT.base = y-TT.rows-1;
|
|
draw_page();
|
|
} else {
|
|
TT.base++;
|
|
tty_jump(0, 0);
|
|
tty_esc("1M");
|
|
tty_jump(0, TT.rows-1);
|
|
draw_line(TT.rows-1);
|
|
}
|
|
}
|
|
draw_status();
|
|
y -= TT.base;
|
|
|
|
// Display cursor and flush output
|
|
highlight(x, y, FLAG(r) ? 3 : side);
|
|
xflush(1);
|
|
|
|
// Wait for next key
|
|
key = scan_key(TT.keybuf, -1);
|
|
|
|
// Window resized?
|
|
if (key == -3) {
|
|
toys.signal = 0;
|
|
terminal_size(&TT.cols, &TT.rows);
|
|
if (TT.rows) TT.rows--;
|
|
draw_page();
|
|
continue;
|
|
}
|
|
|
|
// Various popular ways to quit...
|
|
if (key==-1||key==('C'-'@')||key==('Q'-'@')||key==27||key=='q') break;
|
|
highlight(x, y, 2);
|
|
|
|
if (key == ('J'-'@') || key == ':' || key == '-' || key == '+') {
|
|
// Jump (relative or absolute)
|
|
char initial[2] = {}, *s = 0;
|
|
long long val;
|
|
|
|
if (key == '-' || key == '+') *initial = key;
|
|
if (!prompt("Jump to", initial)) continue;
|
|
|
|
val = estrtol(TT.input, &s, 0);
|
|
if (!errno && s && !*s) {
|
|
if (*TT.input == '-' || *TT.input == '+') TT.pos += val;
|
|
else TT.pos = val;
|
|
}
|
|
continue;
|
|
} else if (key == ('F'-'@') || key == '/') { // Find
|
|
if (!prompt("Find", TT.search ? TT.search : "")) continue;
|
|
|
|
// TODO: parse hex escapes in input, and record length to support \0
|
|
free(TT.search);
|
|
TT.search = xstrdup(TT.input);
|
|
find_next(TT.pos);
|
|
} else if (TT.search && (key == ('G'-'@') || key == 'n')) { // Find next
|
|
if (TT.pos < TT.len) find_next(TT.pos+1);
|
|
} else if (TT.search && (key == ('D'-'@') || key == 'p')) { // Find previous
|
|
if (TT.pos > 0) find_prev(TT.pos-1);
|
|
}
|
|
|
|
// Remove cursor
|
|
highlight(x, y, 2);
|
|
|
|
// Hex digit?
|
|
if (key>='a' && key<='f') key-=32;
|
|
if (!FLAG(r) && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
|
|
if (!side) {
|
|
long long *ll = (long long *)toybuf;
|
|
|
|
ll[TT.undo] = TT.pos;
|
|
toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[TT.pos];
|
|
if (TT.undolen < UNDO_LEN) TT.undolen++;
|
|
TT.undo %= UNDO_LEN;
|
|
}
|
|
|
|
i = key - '0';
|
|
if (i>9) i -= 7;
|
|
TT.data[TT.pos] &= 15<<(4*side);
|
|
TT.data[TT.pos] |= i<<(4*!side);
|
|
|
|
if (++side==2) {
|
|
highlight(x, y, side);
|
|
side = 0;
|
|
++TT.pos;
|
|
}
|
|
} else side = 0;
|
|
if (key=='u') {
|
|
if (TT.undolen) {
|
|
long long *ll = (long long *)toybuf;
|
|
|
|
TT.undolen--;
|
|
if (!TT.undo) TT.undo = UNDO_LEN;
|
|
TT.pos = ll[--TT.undo];
|
|
TT.data[TT.pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
|
|
}
|
|
}
|
|
if (key>=256) {
|
|
key -= 256;
|
|
|
|
if (key==KEY_UP) TT.pos -= 16;
|
|
else if (key==KEY_DOWN) TT.pos += 16;
|
|
else if (key==KEY_RIGHT) {
|
|
if (TT.pos<TT.len) TT.pos++;
|
|
} else if (key==KEY_LEFT) {
|
|
if (TT.pos>0) TT.pos--;
|
|
} else if (key==KEY_PGUP) {
|
|
TT.pos -= 16*TT.rows;
|
|
if (TT.pos < 0) TT.pos = 0;
|
|
TT.base = TT.pos/16;
|
|
draw_page();
|
|
} else if (key==KEY_PGDN) {
|
|
TT.pos += 16*TT.rows;
|
|
if (TT.pos > TT.len-1) TT.pos = TT.len-1;
|
|
TT.base = TT.pos/16;
|
|
draw_page();
|
|
} else if (key==KEY_HOME) TT.pos = TT.pos & ~0xf;
|
|
else if (key==KEY_END) TT.pos = TT.pos | 0xf;
|
|
else if (key==(KEY_CTRL|KEY_HOME)) TT.pos = 0;
|
|
else if (key==(KEY_CTRL|KEY_END)) TT.pos = TT.len-1;
|
|
}
|
|
}
|
|
munmap(TT.data, TT.len);
|
|
tty_reset();
|
|
}
|