├── LICENSE ├── README.md ├── a.h ├── buf.c ├── cols.c ├── dec.c ├── err.c ├── mkfile ├── undo.c ├── vexed.c └── vexed.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 phil9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vexed 2 | A visual hex editor for plan9. 3 | 4 | ![vexed](vexed.png) 5 | 6 | Move using the arrow keys or select using the mouse. 7 | Scroll with page up/down, the mouse wheel or using the scrollbar. 8 | Home and End keys go to the beginning and end of the file respectively. 9 | `g` go to given location which can be entered in decimal or in hexadecimal (starting with 0x). 10 | `i` inserts a byte before the current selection. 11 | `p` inserts a byte after the current selection. 12 | `x` deletes the currently selected byte. 13 | `.` edit selected byte in binary. 14 | `u` undo last edit. 15 | `r` redo last undo'ed edit. 16 | `s` save file. 17 | `l` look for a byte sequence within the file. 18 | `n` go to next matching occurence of looked sequence. 19 | `?` show the decode window that displays bytes as numbers of various sizes. 20 | Del or q exit the program. 21 | 22 | Feel free to drop me an email would you have any feature request. 23 | 24 | ## Usage 25 | ```sh 26 | % mk install 27 | % vexed 28 | ``` 29 | 30 | ## License 31 | MIT 32 | 33 | ## Bugs 34 | Nope, only features! 35 | 36 | -------------------------------------------------------------------------------- /a.h: -------------------------------------------------------------------------------- 1 | /* BUFFER */ 2 | typedef struct Buffer Buffer; 3 | 4 | struct Buffer 5 | { 6 | uchar *data; 7 | usize count; 8 | usize size; 9 | }; 10 | 11 | int readfile(Buffer*, char*); 12 | int writefile(Buffer*, char*); 13 | int delete(Buffer*, int); 14 | int insert(Buffer*, int); 15 | int append(Buffer*, int); 16 | 17 | /* UNDO */ 18 | typedef struct Undo Undo; 19 | 20 | enum 21 | { 22 | Udelete, 23 | Uinsert, 24 | Uappend, 25 | Uset, 26 | }; 27 | 28 | struct Undo 29 | { 30 | int action; 31 | int index; 32 | uchar value; 33 | uchar newvalue; 34 | int modified; 35 | }; 36 | 37 | int canundo(void); 38 | void undo(Undo*); 39 | int canredo(void); 40 | void redo(Undo*); 41 | void pushundo(int, int, uchar, uchar, int); 42 | void patchundo(uchar); 43 | 44 | /* DECODE */ 45 | void showdec(uchar[8], Mousectl*, Keyboardctl*); 46 | 47 | /* ERROR */ 48 | void showerr(const char*, Mousectl*, Keyboardctl*); 49 | 50 | /* COLORS */ 51 | enum 52 | { 53 | BACK, 54 | ADDR, 55 | HEX, 56 | ASCII, 57 | HHEX, 58 | DHEX, 59 | HIGH, 60 | SCROLL, 61 | NCOLS, 62 | }; 63 | 64 | Image* cols[NCOLS]; 65 | void initcols(int); 66 | -------------------------------------------------------------------------------- /buf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "a.h" 7 | 8 | int 9 | readfile(Buffer *buf, char *filename) 10 | { 11 | int fd; 12 | long r; 13 | 14 | buf->count = 0; 15 | buf->size = 8192; 16 | buf->data = malloc(buf->size); 17 | if(buf->data == nil) 18 | return -1; 19 | fd = open(filename, OREAD); 20 | if(fd < 0) 21 | return -1; 22 | for(;;){ 23 | r = read(fd, buf->data + buf->count, buf->size - buf->count); 24 | if(r < 0) 25 | return -1; 26 | if(r == 0) 27 | break; 28 | buf->count += r; 29 | if(buf->count == buf->size){ 30 | buf->size *= 1.5; 31 | buf->data = realloc(buf->data, buf->size); 32 | if(buf->data == nil) 33 | return -1; 34 | } 35 | } 36 | buf->data[buf->count] = 0; 37 | close(fd); 38 | return 0; 39 | } 40 | 41 | int 42 | writefile(Buffer *buf, char *filename) 43 | { 44 | int fd, n; 45 | 46 | fd = open(filename, OWRITE|OTRUNC); 47 | if(fd < 0) 48 | return -1; 49 | n = write(fd, buf->data, buf->count); 50 | if(n < 0 || n != buf->count) 51 | return -1; 52 | close(fd); 53 | return 0; 54 | } 55 | 56 | int 57 | delete(Buffer *buf, int index) 58 | { 59 | if(index != buf->count - 1) 60 | memmove(&buf->data[index], &buf->data[index + 1], buf->count - index); 61 | buf->count -= 1; 62 | /* TODO: is that really what we want ? */ 63 | if(buf->count == 0){ 64 | buf->count = 1; 65 | buf->data[0] = 0; 66 | } 67 | return 0; 68 | } 69 | 70 | int 71 | insert(Buffer *buf, int index) 72 | { 73 | if(buf->count == buf->size){ 74 | buf->size *= 1.5; 75 | buf->data = realloc(buf->data, buf->size); 76 | if(buf->data == nil) 77 | return -1; 78 | } 79 | buf->count += 1; 80 | memmove(&buf->data[index + 1], &buf->data[index], buf->count - index); 81 | buf->data[index] = 0; 82 | buf->data[buf->count] = 0; 83 | return 1; 84 | } 85 | 86 | int 87 | append(Buffer *buf, int index) 88 | { 89 | return insert(buf, index + 1); 90 | } 91 | -------------------------------------------------------------------------------- /cols.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "a.h" 7 | 8 | Image* 9 | ecolor(ulong n) 10 | { 11 | Image *i; 12 | 13 | i = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, n); 14 | if(i == nil) 15 | sysfatal("allocimage: %r"); 16 | return i; 17 | } 18 | 19 | void 20 | initcols(int reverse) 21 | { 22 | if(reverse){ 23 | cols[BACK] = display->black; 24 | cols[ADDR] = ecolor(DPurpleblue); 25 | cols[HEX] = display->white; 26 | cols[ASCII] = ecolor(DPurpleblue); 27 | cols[HHEX] = display->black; 28 | cols[DHEX] = ecolor(0xAAAAAAFF^reverse); 29 | cols[HIGH] = ecolor(DPurpleblue); 30 | cols[SCROLL] = ecolor(0x999999FF^reverse); 31 | }else{ 32 | cols[BACK] = display->white; 33 | cols[ADDR] = ecolor(DGreygreen); 34 | cols[HEX] = display->black; 35 | cols[ASCII] = ecolor(DGreygreen); 36 | cols[HHEX] = display->black; 37 | cols[DHEX] = ecolor(0xAAAAAAFF); 38 | cols[HIGH] = ecolor(0xCCCCCCFF); 39 | cols[SCROLL] = ecolor(0x999999FF); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /dec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "a.h" 8 | 9 | enum { Padding = 12, }; 10 | 11 | u8int 12 | u8(uchar buf[8]) 13 | { 14 | return (u8int)buf[7]; 15 | } 16 | 17 | s8int 18 | s8(uchar b[8]) 19 | { 20 | return (s8int)b[7]; 21 | } 22 | 23 | u16int 24 | u16(uchar b[8]) 25 | { 26 | u16int r; 27 | int i; 28 | 29 | r = 0; 30 | for(i = 0; i < 2; i++) 31 | r += (u16int)(b[6 + i] << 8*(2-i-1)); 32 | return r; 33 | } 34 | 35 | s16int 36 | s16(uchar b[8]) 37 | { 38 | s16int r; 39 | int i; 40 | 41 | r = 0; 42 | for(i = 0; i < 2; i++) 43 | r += (s16int)(b[6 + i] << 8*(2-i-1)); 44 | return r; 45 | } 46 | 47 | u32int 48 | u32(uchar b[8]) 49 | { 50 | u32int r; 51 | int i; 52 | 53 | r = 0; 54 | for(i = 0; i < 4; i++) 55 | r += (u32int)(b[4 + i] << 8*(4-i-1)); 56 | return r; 57 | } 58 | 59 | s32int 60 | s32(uchar b[8]) 61 | { 62 | s32int r; 63 | int i; 64 | 65 | r = 0; 66 | for(i = 0; i < 4; i++) 67 | r += (s32int)(b[4 + i] << 8*(4-i-1)); 68 | return r; 69 | } 70 | 71 | u64int 72 | u64(uchar b[8]) 73 | { 74 | u64int r; 75 | int i; 76 | 77 | r = 0; 78 | for(i = 0; i < 8; i++) 79 | r += (u64int)(b[i] << 8*(8-i-1)); 80 | return r; 81 | } 82 | 83 | s64int 84 | s64(uchar b[8]) 85 | { 86 | s64int r; 87 | int i; 88 | 89 | r = 0; 90 | for(i = 0; i < 8; i++) 91 | r += (s64int)(b[i] << 8*(8-i-1)); 92 | return r; 93 | } 94 | 95 | float 96 | f32(uchar b[8]) 97 | { 98 | union { uchar b[4]; float f; } v; 99 | 100 | memcpy(v.b, &b[4], 4); 101 | return v.f; 102 | } 103 | 104 | double 105 | f64(uchar b[8]) 106 | { 107 | union { uchar b[8]; double d; } v; 108 | 109 | memcpy(v.b, b, 8); 110 | return v.d; 111 | } 112 | 113 | void 114 | dec(uchar buf[8], Image *b, Point o, Point p, Image *fg) 115 | { 116 | char tmp[64] = {0}; 117 | int n; 118 | 119 | p = string(b, p, fg, ZP, font, " in: "); 120 | for(n = 0; n < 8; n++){ 121 | snprint(tmp, sizeof tmp, "%02X ", buf[n]); 122 | p = string(b, p, fg, ZP, font, tmp); 123 | } 124 | p = addpt(o, Pt(Padding, 2*Padding + font->height)); 125 | snprint(tmp, sizeof tmp, "%5s %-20ud %5s %-20d", "u8:", u8(buf), "s8:", s8(buf)); 126 | string(b, p, fg, ZP, font, tmp); 127 | p.y += font->height; 128 | snprint(tmp, sizeof tmp, "%5s %-20ud %5s %-20d", "u16:", u16(buf), "s16:", s16(buf)); 129 | string(b, p, fg, ZP, font, tmp); 130 | p.y += font->height; 131 | snprint(tmp, sizeof tmp, "%5s %-20ud %5s %-20d", "u32:", u32(buf), "s32:", s32(buf)); 132 | string(b, p, fg, ZP, font, tmp); 133 | p.y += font->height; 134 | snprint(tmp, sizeof tmp, "%5s %-20llud %5s %-20lld", "u64:", u64(buf), "s64:", s64(buf)); 135 | string(b, p, fg, ZP, font, tmp); 136 | p.y += font->height; 137 | snprint(tmp, sizeof tmp, "%5s %-20e %5s %-20e", "f32:", f32(buf), "f64:", f64(buf)); 138 | string(b, p, fg, ZP, font, tmp); 139 | } 140 | 141 | void 142 | showdec(uchar buf[8], Mousectl *mctl, Keyboardctl *kctl) 143 | { 144 | Alt alts[3]; 145 | Rectangle r, sc; 146 | Point o, p; 147 | Image *b, *save, *bg, *fg, *bord; 148 | int done, h, w, sw; 149 | Mouse m; 150 | Rune k; 151 | 152 | alts[0].op = CHANRCV; 153 | alts[0].c = mctl->c; 154 | alts[0].v = &m; 155 | alts[1].op = CHANRCV; 156 | alts[1].c = kctl->c; 157 | alts[1].v = &k; 158 | alts[2].op = CHANEND; 159 | alts[2].c = nil; 160 | alts[2].v = nil; 161 | while(nbrecv(kctl->c, nil)==1) 162 | ; 163 | bg = allocimagemix(display, DPaleyellow, DWhite); 164 | bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen); 165 | fg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000ff); 166 | done = 0; 167 | save = nil; 168 | sw = stringwidth(font, " "); 169 | h = Padding + 6*font->height + Padding + Padding; 170 | w = Padding + 5*sw + 20*sw + 2*sw + 5*sw + 20*sw + Padding; 171 | b = screen; 172 | sc = b->clipr; 173 | replclipr(b, 0, b->r); 174 | while(!done){ 175 | o = addpt(screen->r.min, Pt((Dx(screen->r)-w)/2, (Dy(screen->r)-h)/2)); 176 | r = Rect(o.x, o.y, o.x+w, o.y+h); 177 | if(save==nil){ 178 | save = allocimage(display, r, b->chan, 0, DNofill); 179 | if(save==nil) 180 | break; 181 | draw(save, r, b, nil, r.min); 182 | } 183 | draw(b, r, bg, nil, ZP); 184 | border(b, r, 2, bord, ZP); 185 | p = addpt(o, Pt(Padding, Padding)); 186 | dec(buf, b, o, p, fg); 187 | flushimage(display, 1); 188 | if(b!=screen || !eqrect(screen->clipr, sc)){ 189 | freeimage(save); 190 | save = nil; 191 | } 192 | b = screen; 193 | sc = b->clipr; 194 | replclipr(b, 0, b->r); 195 | switch(alt(alts)){ 196 | default: 197 | continue; 198 | break; 199 | case 1: 200 | done = (k=='\n' || k==Kesc); 201 | break; 202 | case 0: 203 | done = m.buttons&1 && ptinrect(m.xy, r); 204 | break; 205 | } 206 | if(save){ 207 | draw(b, save->r, save, nil, save->r.min); 208 | freeimage(save); 209 | save = nil; 210 | } 211 | 212 | } 213 | replclipr(b, 0, sc); 214 | flushimage(display, 1); 215 | freeimage(bg); 216 | freeimage(fg); 217 | } 218 | -------------------------------------------------------------------------------- /err.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | enum { Padding = 12, }; 9 | 10 | void 11 | showerr(const char *message, Mousectl *mctl, Keyboardctl *kctl) 12 | { 13 | Alt alts[3]; 14 | Rectangle r, sc; 15 | Point o, p; 16 | Image *b, *save, *bg, *fg; 17 | int done, h, w; 18 | Mouse m; 19 | Rune k; 20 | 21 | alts[0].op = CHANRCV; 22 | alts[0].c = mctl->c; 23 | alts[0].v = &m; 24 | alts[1].op = CHANRCV; 25 | alts[1].c = kctl->c; 26 | alts[1].v = &k; 27 | alts[2].op = CHANEND; 28 | alts[2].c = nil; 29 | alts[2].v = nil; 30 | while(nbrecv(kctl->c, nil)==1) 31 | ; 32 | bg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xf8d7daff); 33 | fg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x721c24ff); 34 | done = 0; 35 | save = nil; 36 | h = Padding + font->height + Padding; 37 | w = Padding + stringwidth(font, message) + Padding; 38 | b = screen; 39 | sc = b->clipr; 40 | replclipr(b, 0, b->r); 41 | while(!done){ 42 | o = addpt(screen->r.min, Pt((Dx(screen->r)-w)/2, (Dy(screen->r)-h)/2)); 43 | r = Rect(o.x, o.y, o.x+w, o.y+h); 44 | if(save==nil){ 45 | save = allocimage(display, r, b->chan, 0, DNofill); 46 | if(save==nil) 47 | break; 48 | draw(save, r, b, nil, r.min); 49 | } 50 | draw(b, r, bg, nil, ZP); 51 | border(b, r, 2, fg, ZP); 52 | p = addpt(o, Pt(Padding, Padding)); 53 | string(b, p, fg, ZP, font, message); 54 | flushimage(display, 1); 55 | if(b!=screen || !eqrect(screen->clipr, sc)){ 56 | freeimage(save); 57 | save = nil; 58 | } 59 | b = screen; 60 | sc = b->clipr; 61 | replclipr(b, 0, b->r); 62 | switch(alt(alts)){ 63 | default: 64 | continue; 65 | break; 66 | case 1: 67 | done = (k=='\n' || k==Kesc); 68 | break; 69 | case 0: 70 | done = m.buttons&1 && ptinrect(m.xy, r); 71 | break; 72 | } 73 | if(save){ 74 | draw(b, save->r, save, nil, save->r.min); 75 | freeimage(save); 76 | save = nil; 77 | } 78 | 79 | } 80 | replclipr(b, 0, sc); 81 | flushimage(display, 1); 82 | freeimage(bg); 83 | freeimage(fg); 84 | } 85 | -------------------------------------------------------------------------------- /mkfile: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "a.h" 7 | 8 | enum { Stacksize = 1024 }; 9 | Undo ustack[Stacksize]; 10 | int ucount = 0; 11 | int ucur = -1; 12 | 13 | int 14 | canundo(void) 15 | { 16 | return ucur >= 0; 17 | } 18 | 19 | void 20 | undo(Undo *undo) 21 | { 22 | undo->action = ustack[ucur].action; 23 | undo->index = ustack[ucur].index; 24 | undo->value = ustack[ucur].value; 25 | undo->newvalue = ustack[ucur].newvalue; 26 | undo->modified = ustack[ucur].modified; 27 | ucur -= 1; 28 | } 29 | 30 | int 31 | canredo(void) 32 | { 33 | return ucur < ucount - 1; 34 | } 35 | 36 | void 37 | redo(Undo *undo) 38 | { 39 | ucur += 1; 40 | undo->action = ustack[ucur].action; 41 | undo->index = ustack[ucur].index; 42 | undo->value = ustack[ucur].value; 43 | undo->newvalue = ustack[ucur].newvalue; 44 | undo->modified = ustack[ucur].modified; 45 | } 46 | 47 | void 48 | pushundo(int action, int index, uchar value, uchar newvalue, int modified) 49 | { 50 | if(ucur == Stacksize - 1) 51 | return; 52 | ucur += 1; 53 | ucount = ucur + 1; 54 | ustack[ucur].action = action; 55 | ustack[ucur].index = index; 56 | ustack[ucur].value = value; 57 | ustack[ucur].newvalue = newvalue; 58 | ustack[ucur].modified = modified; 59 | } 60 | 61 | void 62 | patchundo(uchar value) 63 | { 64 | ustack[ucur].newvalue = value; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /vexed.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "a.h" 9 | 10 | void redraw(void); 11 | void drawstatus(void); 12 | void drawselchange(int); 13 | 14 | enum 15 | { 16 | Emouse, 17 | Eresize, 18 | Ekeyboard, 19 | }; 20 | 21 | enum 22 | { 23 | Padding = 4, 24 | Spacing = 8, 25 | Scrollwidth = 14, 26 | }; 27 | 28 | enum { 29 | Mundo, 30 | Mredo, 31 | Msnarfhex, 32 | Msnarfascii, 33 | Mdecode, 34 | Mbinary, 35 | Minsert, 36 | Mappend, 37 | Mdelete, 38 | Mgoto, 39 | Mlook, 40 | Mnext 41 | }; 42 | char *menu2str[] = { 43 | "undo", 44 | "redo", 45 | "snarf hex", 46 | "snarf ascii", 47 | "decode", 48 | "binary", 49 | "insert", 50 | "append", 51 | "delete", 52 | "goto", 53 | "look", 54 | "next", 55 | 0 56 | }; 57 | Menu menu2 = { menu2str }; 58 | 59 | enum { Msave, Mquit, }; 60 | char *menu3str[] = { "save", "quit", 0 }; 61 | Menu menu3 = { menu3str }; 62 | 63 | const char *filename; 64 | int modified; 65 | Buffer buf; 66 | int blines; 67 | Mousectl *mctl; 68 | Keyboardctl *kctl; 69 | int sw; 70 | int scrollsize; 71 | Rectangle scrollr; 72 | Rectangle viewr; 73 | Rectangle statusr; 74 | int scrolling; 75 | int lastbuttons; 76 | int nlines; 77 | int offset; 78 | int sel = 0; 79 | int sele = -1; 80 | uchar sbuf[255] = {0}; 81 | int nsbuf; 82 | char sstr[256] = {0}; 83 | int sindex = -1; 84 | int dimnul; 85 | 86 | int 87 | hexval(Rune k) 88 | { 89 | int v; 90 | 91 | if(isdigit(k)) 92 | v = k - '0'; 93 | else 94 | v = 10 + tolower(k) - 'a'; 95 | return v; 96 | } 97 | 98 | int 99 | search(int from) 100 | { 101 | char *s, *p; 102 | int len, oldsel; 103 | 104 | s = (char*)buf.data + from; 105 | for(;;){ 106 | len = s - (char*)buf.data; 107 | if(len >= buf.count) 108 | break; 109 | p = memchr(s, sbuf[0], buf.count - len); 110 | if(p == nil || (nsbuf > 1 && memcmp(p, sbuf, nsbuf) != 0)){ 111 | s = p + 1; 112 | continue; 113 | } 114 | oldsel = sel; 115 | sel = p - (char*)buf.data; 116 | drawselchange(oldsel); 117 | sindex = sel; 118 | return 1; 119 | } 120 | return 0; 121 | } 122 | 123 | void 124 | xundo(void) 125 | { 126 | Undo u; 127 | 128 | if(!canundo()) 129 | return; 130 | undo(&u); 131 | switch(u.action){ 132 | case Udelete: 133 | if(insert(&buf, u.index) < 0) 134 | sysfatal("insert: %r"); 135 | buf.data[u.index] = u.value; 136 | break; 137 | case Uinsert: 138 | if(delete(&buf, u.index) < 0) 139 | sysfatal("delete: %r"); 140 | break; 141 | case Uappend: 142 | if(delete(&buf, u.index + 1) < 0) 143 | sysfatal("delete: %r"); 144 | break; 145 | case Uset: 146 | buf.data[u.index] = u.value; 147 | break; 148 | } 149 | sel = u.index; 150 | modified = u.modified; 151 | blines = buf.count / 16; 152 | redraw(); 153 | } 154 | 155 | void 156 | xredo(void) 157 | { 158 | Undo u; 159 | 160 | if(!canredo()) 161 | return; 162 | redo(&u); 163 | switch(u.action){ 164 | case Udelete: 165 | if(delete(&buf, u.index) < 0) 166 | sysfatal("insert: %r"); 167 | sel = u.index; 168 | break; 169 | case Uinsert: 170 | if(insert(&buf, u.index) < 0) 171 | sysfatal("insert: %r"); 172 | sel = u.index; 173 | break; 174 | case Uappend: 175 | if(insert(&buf, u.index + 1) < 0) 176 | sysfatal("insert: %r"); 177 | sel = u.index + 1; 178 | break; 179 | case Uset: 180 | buf.data[u.index] = u.newvalue; 181 | break; 182 | } 183 | if(sel == buf.count) 184 | --sel; 185 | modified = 1; 186 | blines = buf.count / 16; 187 | redraw(); 188 | } 189 | 190 | void 191 | xsnarfhex(void) 192 | { 193 | int fd, i, n, m; 194 | 195 | fd = open("/dev/snarf", OWRITE|OCEXEC); 196 | if(fd < 0) 197 | return; 198 | if(sele == -1) 199 | fprint(fd, "%02X", buf.data[sel]); 200 | else{ 201 | n = sel < sele ? sel : sele; 202 | m = sel < sele ? sele : sel; 203 | for(i = n; i <= m; i++) 204 | fprint(fd, "%02X", buf.data[i]); 205 | } 206 | close(fd); 207 | } 208 | 209 | void 210 | xsnarfascii(void) 211 | { 212 | int fd, n, m; 213 | 214 | fd = open("/dev/snarf", OWRITE|OCEXEC); 215 | if(fd < 0) 216 | return; 217 | if(sele == -1) 218 | write(fd, &buf.data[sel], 1); 219 | else{ 220 | n = sel < sele ? sel : sele; 221 | m = sel < sele ? sele : sel; 222 | write(fd, &buf.data[n], m - n + 1); 223 | } 224 | close(fd); 225 | } 226 | 227 | void 228 | xgoto(void) 229 | { 230 | char b[16] = {0}, *endp; 231 | int n; 232 | 233 | if(enter("Address:", b, sizeof b, mctl, kctl, nil) <= 0) 234 | return; 235 | n = strtol(b, &endp, 0); 236 | if(endp == nil || endp == b) 237 | return; 238 | if(n < 0 || n >= buf.count) 239 | return; 240 | sel = n; 241 | redraw(); 242 | } 243 | 244 | void 245 | xdelete(void) 246 | { 247 | pushundo(Udelete, sel, buf.data[sel], 0, modified); 248 | if(delete(&buf, sel) < 0) 249 | sysfatal("delete: %r"); 250 | if(sel == buf.count) 251 | --sel; 252 | modified = 1; 253 | blines = buf.count/16; 254 | redraw(); 255 | } 256 | 257 | void 258 | xinsert(void) 259 | { 260 | pushundo(Uinsert, sel, 0, 0, modified); 261 | if(insert(&buf, sel) < 0) 262 | sysfatal("insert: %r"); 263 | modified = 1; 264 | blines = buf.count/16; 265 | redraw(); 266 | } 267 | 268 | void 269 | xappend(void) 270 | { 271 | pushundo(Uappend, sel, 0, 0, modified); 272 | if(append(&buf, sel) < 0) 273 | sysfatal("append: %r"); 274 | sel += 1; 275 | modified = 1; 276 | blines = buf.count/16; 277 | redraw(); 278 | } 279 | 280 | void 281 | xbinary(void) 282 | { 283 | char tmp[9] = {0}; 284 | char msg[19] = "Binary (xxxxxxxx):"; 285 | char out = 0; 286 | int n, i; 287 | 288 | for(i = 0; i < 8; i++) 289 | msg[15 - i] = (buf.data[sel] & 1 << i) ? '1' : '0'; 290 | n = enter(msg, tmp, sizeof tmp, mctl, kctl, nil); 291 | if(n <= 0) 292 | return; 293 | for(i = 0; i < 8 && i < n; i++){ 294 | if(tmp[7-i] != '0') 295 | out |= 1 << i; 296 | } 297 | buf.data[sel] = out; 298 | modified = 1; 299 | redraw(); 300 | } 301 | 302 | void 303 | xlook(void) 304 | { 305 | char tmp[255] = {0}; 306 | int n, i; 307 | 308 | n = enter("Look:", tmp, sizeof tmp, mctl, kctl, nil); 309 | if(n <= 0) 310 | return; 311 | if(n%2 != 0){ 312 | showerr("invalid byte sequence", mctl, kctl); 313 | return; 314 | } 315 | nsbuf = 0; 316 | sindex = -1; 317 | for(i = 0; i < n; i += 2){ 318 | if(!(isxdigit(tmp[i]) && isxdigit(tmp[i+1]))){ 319 | showerr("invalid character in byte sequence", mctl, kctl); 320 | return; 321 | } 322 | sbuf[nsbuf++] = 16*hexval(tmp[i]) + hexval(tmp[i+1]); 323 | } 324 | sbuf[nsbuf] = 0; 325 | snprint(sstr, sizeof sstr, "/%s", tmp); 326 | if(!search(sel)){ 327 | sindex = -1; 328 | } 329 | } 330 | 331 | void 332 | xnext(void) 333 | { 334 | if(sindex == -1) 335 | return; 336 | if(!search(sindex + 1)) 337 | search(0); 338 | } 339 | 340 | void 341 | xdecode(void) 342 | { 343 | uchar b[8] = {0}; 344 | int n, m, c; 345 | 346 | if(sele == -1){ 347 | b[7] = buf.data[sel]; 348 | }else{ 349 | n = sel < sele ? sel : sele; 350 | m = sel < sele ? sele : sel; 351 | c = m - n + 1; 352 | if(c > 8){ 353 | showerr("cannot decode more than 8 bytes", mctl, kctl); 354 | return; 355 | } 356 | memcpy(&b[8 - c], &buf.data[n], c); 357 | } 358 | showdec(b, mctl, kctl); 359 | } 360 | 361 | void 362 | save(void) 363 | { 364 | if(!modified) 365 | return; 366 | if(writefile(&buf, filename) < 0) 367 | sysfatal("writefile: %r"); 368 | modified = 0; 369 | redraw(); 370 | } 371 | 372 | int 373 | selvisible(void) 374 | { 375 | int l; 376 | 377 | l = sel/16; 378 | return offset <= l && l < offset+nlines; 379 | } 380 | 381 | int 382 | ensureselvisible(void) 383 | { 384 | if(selvisible()) 385 | return 0; 386 | offset = sel/16; 387 | return 1; 388 | } 389 | 390 | int 391 | isselected(int index) 392 | { 393 | int selected; 394 | 395 | if(sele == -1) 396 | selected = index == sel; 397 | else if(sel < sele) 398 | selected = index >= sel && index <= sele; 399 | else 400 | selected = index >= sele && index <= sel; 401 | return selected; 402 | } 403 | 404 | void 405 | drawline(int line) 406 | { 407 | int y, index, i, n, selected, hs; 408 | char b[8] = {0}, *s; 409 | Point p; 410 | Point p2; 411 | Image *c; 412 | 413 | y = viewr.min.y + line * font->height; 414 | index = (line + offset)*16; 415 | if(index >= buf.count) 416 | return; 417 | draw(screen, Rect(viewr.min.x, y, viewr.max.x, y + font->height), cols[BACK], nil, ZP); 418 | p = Pt(viewr.min.x, y); 419 | if(index/16 == sel/16){ 420 | n = snprint(b, sizeof b, "%06X", sel); 421 | p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[ADDR], ZP); 422 | }else{ 423 | n = snprint(b, sizeof b, "%06X", index); 424 | p = stringn(screen, p, cols[ADDR], ZP, font, b, n); 425 | } 426 | p.x += 2*Spacing; 427 | p2 = addpt(p, Pt(16*3*sw - sw + 2*Spacing, 0)); 428 | for(i = 0; i < 16; i++) { 429 | if(index + i >= buf.count) 430 | break; 431 | n = snprint(b, sizeof b, "%02X", buf.data[index + i]); 432 | s = isprint(buf.data[index + i]) ? (char*)&buf.data[index + i] : "."; 433 | selected = isselected(index + i); 434 | if(selected){ 435 | p = stringnbg(screen, p, cols[HHEX], ZP, font, b, n, cols[HIGH], ZP); 436 | p2 = stringnbg(screen, p2, cols[BACK], ZP, font, s, 1, cols[ASCII], ZP); 437 | }else{ 438 | c = dimnul && buf.data[index+i] == 0 ? cols[DHEX] : cols[HEX]; 439 | p = stringn(screen, p, c, ZP, font, b, n); 440 | p2 = stringn(screen, p2, cols[ASCII], ZP, font, s, 1); 441 | } 442 | hs = 0; 443 | if(selected && sele != -1){ 444 | if(sel < sele) 445 | hs = index + i != sele; 446 | else 447 | hs = index + i != sel; 448 | } 449 | if(hs) 450 | p = stringnbg(screen, p, cols[BACK], ZP, font, " ", 1, cols[HIGH], ZP); 451 | else 452 | p = stringn(screen, p, cols[BACK], ZP, font, " ", 1); 453 | } 454 | } 455 | 456 | void 457 | drawselchange(int oldsel) 458 | { 459 | int ol, nl; 460 | 461 | if(ensureselvisible()) 462 | redraw(); 463 | else{ 464 | ol = oldsel/16 - offset; 465 | nl = sel/16 - offset; 466 | if(ol != nl) 467 | drawline(ol); 468 | drawline(nl); 469 | drawstatus(); 470 | flushimage(display, 1); 471 | } 472 | } 473 | 474 | void 475 | drawstatus(void) 476 | { 477 | char b[64] = {0}; 478 | Point p; 479 | int x, y; 480 | 481 | draw(screen, statusr, cols[BACK], nil, ZP); 482 | y = statusr.min.y + Padding; 483 | p = string(screen, Pt(statusr.min.x + Padding, y), cols[HEX], ZP, font, filename); 484 | if(modified) 485 | string(screen, p, cols[SCROLL], ZP, font, " (modified)"); 486 | snprint(b, sizeof b, "%d/%zld (%d%%)", sel+1, buf.count, (int)((100.0 * sel) / buf.count + 0.5)); 487 | x = statusr.max.x - stringwidth(font, b) - Padding; 488 | string(screen, Pt(x, y), cols[HEX], ZP, font, b); 489 | y = statusr.max.y; 490 | line(screen, Pt(statusr.min.x, y), Pt(statusr.max.x, y), 0, 0, 0, cols[HEX], ZP); 491 | } 492 | 493 | void 494 | redraw(void) 495 | { 496 | int i, h, y, ye; 497 | Rectangle scrposr; 498 | 499 | draw(screen, screen->r, cols[BACK], nil, ZP); 500 | draw(screen, scrollr, cols[SCROLL], nil, ZP); 501 | border(screen, scrollr, 0, cols[HEX], ZP); 502 | if(blines > 0){ 503 | h = ((double)nlines / blines) * Dy(scrollr); 504 | y = ((double)offset / blines) * Dy(scrollr); 505 | if(h == 0) h = 5; 506 | ye = scrollr.min.y + y + h - 1; 507 | if(ye >= scrollr.max.y) 508 | ye = scrollr.max.y - 1; 509 | scrposr = Rect(scrollr.min.x, scrollr.min.y + y, scrollr.max.x - 1, ye); 510 | }else 511 | scrposr = Rect(scrollr.min.x, scrollr.min.y, scrollr.max.x-1, scrollr.max.y); 512 | draw(screen, scrposr, cols[BACK], nil, ZP); 513 | for(i = 0; i < nlines; i++) 514 | drawline(i); 515 | drawstatus(); 516 | flushimage(display, 1); 517 | } 518 | 519 | void 520 | clampoffset(void) 521 | { 522 | if(offset < 0) 523 | offset = 0; 524 | if(offset + blines%nlines >= blines) 525 | offset = blines - blines%nlines; 526 | } 527 | 528 | int 529 | scroll(int lines) 530 | { 531 | if(blines <= nlines) 532 | return 0; 533 | if(lines < 0 && offset == 0) 534 | return 0; 535 | if(lines > 0 && offset + nlines >= blines){ 536 | return 0; 537 | } 538 | offset += lines; 539 | clampoffset(); 540 | sel += lines * 16; 541 | if(sel < 0) 542 | sel = 0; 543 | else if(sel >= buf.count) 544 | sel = buf.count - 1; 545 | redraw(); 546 | return 1; 547 | } 548 | 549 | int 550 | indexat(Point p) 551 | { 552 | int row, col, index; 553 | 554 | row = (p.y - viewr.min.y) / font->height; 555 | col = ((p.x - viewr.min.x) / sw - 8); 556 | if(col < 0 || col > 16*3*sw) 557 | return -1; 558 | index = col/3 + row*16 + offset*16; 559 | if(index >= buf.count) 560 | return -1; 561 | return index; 562 | } 563 | 564 | void 565 | menu2hit(void) 566 | { 567 | int n; 568 | 569 | menu2str[Mnext] = sindex == -1 ? nil : sstr; 570 | n = menuhit(2, mctl, &menu2, nil); 571 | switch(n){ 572 | case Mundo: 573 | xundo(); 574 | break; 575 | case Mredo: 576 | xredo(); 577 | break; 578 | case Msnarfhex: 579 | xsnarfhex(); 580 | break; 581 | case Msnarfascii: 582 | xsnarfascii(); 583 | break; 584 | case Mdecode: 585 | xdecode(); 586 | break; 587 | case Mgoto: 588 | xgoto(); 589 | break; 590 | case Mdelete: 591 | xdelete(); 592 | break; 593 | case Minsert: 594 | xinsert(); 595 | break; 596 | case Mappend: 597 | xappend(); 598 | break; 599 | case Mbinary: 600 | xbinary(); 601 | break; 602 | case Mlook: 603 | xlook(); 604 | break; 605 | case Mnext: 606 | xnext(); 607 | break; 608 | } 609 | } 610 | 611 | void 612 | menu3hit(void) 613 | { 614 | int n; 615 | 616 | n = menuhit(3, mctl, &menu3, nil); 617 | switch(n){ 618 | case Msave: 619 | save(); 620 | break; 621 | case Mquit: 622 | threadexitsall(nil); 623 | break; 624 | } 625 | } 626 | 627 | void 628 | emouse(Mouse *m) 629 | { 630 | int n; 631 | 632 | if(lastbuttons == 0 && m->buttons != 0 && ptinrect(m->xy, scrollr)) 633 | scrolling = 1; 634 | else if(m->buttons == 0) 635 | scrolling = 0; 636 | if(scrolling){ 637 | if(m->buttons == 1){ 638 | n = (m->xy.y - scrollr.min.y) / font->height; 639 | scroll(-n); 640 | }else if(m->buttons == 2){ 641 | offset = (m->xy.y - scrollr.min.y) * blines / Dy(scrollr); 642 | clampoffset(); 643 | sel = offset*16; 644 | redraw(); 645 | }else if(m->buttons == 4){ 646 | n = (m->xy.y - scrollr.min.y) / font->height; 647 | scroll(n); 648 | } 649 | } else if(ptinrect(m->xy, viewr)){ 650 | if(m->buttons == 1){ 651 | n = indexat(m->xy); 652 | if(n >= 0){ 653 | sel = n; 654 | sele = -1; 655 | redraw(); 656 | for(readmouse(mctl); mctl->buttons == 1; readmouse(mctl)){ 657 | n = indexat(mctl->xy); 658 | if(n < 0) 659 | break; 660 | sele = n; 661 | redraw(); 662 | } 663 | } 664 | }else if(m->buttons == 2){ 665 | menu2hit(); 666 | }else if(m->buttons == 4){ 667 | menu3hit(); 668 | }else if(m->buttons == 8){ 669 | scroll(-scrollsize); 670 | }else if(m->buttons == 16){ 671 | scroll(scrollsize); 672 | } 673 | } 674 | lastbuttons = m->buttons; 675 | } 676 | 677 | void 678 | eresize(void) 679 | { 680 | Rectangle sr; 681 | int w, x; 682 | 683 | sr = screen->r; 684 | statusr = Rect(sr.min.x, sr.min.y, sr.max.x, sr.min.y + Padding + font->height + 1); 685 | scrollr = insetrect(Rect(sr.min.x, statusr.max.y+1, sr.min.x+Scrollwidth, sr.max.y), 1); 686 | sw = stringwidth(font, " "); 687 | w = Padding + 6*sw + 2*Spacing + 16*3*sw-sw + 2*Spacing + 16*sw + Padding; 688 | x = scrollr.max.x + Padding; 689 | viewr = insetrect(Rect(x, statusr.max.y + 1, x + w, sr.max.y + Padding - 2), Padding); 690 | nlines = Dy(viewr) / font->height; 691 | scrollsize = mousescrollsize(nlines); 692 | redraw(); 693 | flushimage(display, 1); 694 | } 695 | 696 | void 697 | ekeyboard(Rune k) 698 | { 699 | static long lastk = 0; 700 | static int lastv; 701 | long e; 702 | int oldsel; 703 | 704 | if(!isxdigit(k)) 705 | lastv = -1; 706 | e = time(nil); 707 | oldsel = sel; 708 | if(sele != -1){ 709 | sele = -1; 710 | redraw(); 711 | } 712 | switch(k){ 713 | case 'q': 714 | case Kdel: 715 | threadexitsall(nil); 716 | break; 717 | case Kpgup: 718 | scroll(-nlines); 719 | break; 720 | case Kpgdown: 721 | scroll(nlines); 722 | break; 723 | case Kleft: 724 | if(sel > 0){ 725 | oldsel = sel; 726 | sel--; 727 | drawselchange(oldsel); 728 | } 729 | break; 730 | case Kright: 731 | if(sel < (buf.count - 1)){ 732 | sel++; 733 | drawselchange(oldsel); 734 | } 735 | break; 736 | case Kup: 737 | if(sel >= 16){ 738 | sel -= 16; 739 | drawselchange(oldsel); 740 | } 741 | break; 742 | case Kdown: 743 | if(sel < (buf.count - 16)){ 744 | sel += 16; 745 | drawselchange(oldsel); 746 | } 747 | break; 748 | case Khome: 749 | if(sel != 0){ 750 | sel = 0; 751 | drawselchange(oldsel); 752 | } 753 | break; 754 | case Kend: 755 | if(sel != buf.count - 1){ 756 | sel = buf.count - 1; 757 | if(!selvisible()) 758 | offset = blines - blines%nlines; 759 | redraw(); 760 | } 761 | break; 762 | case 'u': 763 | case 'U': 764 | xundo(); 765 | break; 766 | case 'r': 767 | case 'R': 768 | xredo(); 769 | break; 770 | case 'g': 771 | case 'G': 772 | xgoto(); 773 | break; 774 | case 'x': 775 | case 'X': 776 | xdelete(); 777 | break; 778 | case 'i': 779 | case 'I': 780 | xinsert(); 781 | break; 782 | case 'p': 783 | case 'P': 784 | xappend(); 785 | break; 786 | case '.': 787 | xbinary(); 788 | break; 789 | case 'l': 790 | case 'L': 791 | xlook(); 792 | break; 793 | case 'n': 794 | case 'N': 795 | xnext(); 796 | break; 797 | case 's': 798 | case 'S': 799 | save(); 800 | break; 801 | case '?': 802 | xdecode(); 803 | break; 804 | default: 805 | if(isxdigit(k)){ 806 | if(e - lastk < 2 && lastv > 0) { 807 | buf.data[sel] = lastv*16 + hexval(k); 808 | patchundo(buf.data[sel]); 809 | lastv = -1; 810 | }else{ 811 | lastv = hexval(k); 812 | pushundo(Uset, sel, buf.data[sel], lastv, modified); 813 | buf.data[sel] = lastv; 814 | } 815 | modified = 1; 816 | drawselchange(oldsel); 817 | drawstatus(); 818 | }else{ 819 | lastv = -1; 820 | } 821 | } 822 | lastk = e; 823 | } 824 | 825 | void 826 | usage(void) 827 | { 828 | fprint(2, "usage: %s [-b] \n", argv0); 829 | threadexitsall("usage"); 830 | } 831 | 832 | void 833 | threadmain(int argc, char *argv[]) 834 | { 835 | Mouse m; 836 | Rune k; 837 | Alt alts[] = { 838 | { nil, &m, CHANRCV }, 839 | { nil, nil, CHANRCV }, 840 | { nil, &k, CHANRCV }, 841 | { nil, nil, CHANEND }, 842 | }; 843 | int reverse; 844 | 845 | scrolling = 0; 846 | lastbuttons = 0; 847 | reverse = 0; 848 | dimnul = 0; 849 | ARGBEGIN{ 850 | case 'b': 851 | reverse = ~0xFF; 852 | break; 853 | case 'd': 854 | dimnul = 1; 855 | break; 856 | default: 857 | usage(); 858 | }ARGEND; 859 | if(*argv == nil) 860 | usage(); 861 | filename = *argv; 862 | modified = 0; 863 | if(readfile(&buf, filename) < 0) 864 | sysfatal("readfile: %r"); 865 | blines = buf.count/16; 866 | if(initdraw(nil, nil, argv0) < 0) 867 | sysfatal("initdraw: %r"); 868 | display->locking = 0; 869 | if((mctl = initmouse(nil, screen)) == nil) 870 | sysfatal("initmouse: %r"); 871 | if((kctl = initkeyboard(nil)) == nil) 872 | sysfatal("initkeyboard: %r"); 873 | initcols(reverse); 874 | alts[Emouse].c = mctl->c; 875 | alts[Eresize].c = mctl->resizec; 876 | alts[Ekeyboard].c = kctl->c; 877 | eresize(); 878 | for(;;){ 879 | switch(alt(alts)){ 880 | case Emouse: 881 | emouse(&m); 882 | break; 883 | case Eresize: 884 | if(getwindow(display, Refnone) < 0) 885 | sysfatal("getwindow: %r"); 886 | eresize(); 887 | break; 888 | case Ekeyboard: 889 | ekeyboard(k); 890 | break; 891 | } 892 | } 893 | } 894 | 895 | -------------------------------------------------------------------------------- /vexed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telephil9/vexed/5a154a15f62c9d322ea4c1aa22b55be88b827625/vexed.png --------------------------------------------------------------------------------