├── README.md ├── makefile └── she.c /README.md: -------------------------------------------------------------------------------- 1 | # she 2 | simple hex editor 3 | 4 | ![preview](https://i.imgur.com/y90gmyK.png) 5 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | LDFLAGS = -static -ltermbox 2 | CFLAGS = -g 3 | 4 | all: she 5 | 6 | # cause GNU make doesn't know how to make files 7 | .c: 8 | ${CC} -o $@ ${CFLAGS} $< ${LDFLAGS} 9 | -------------------------------------------------------------------------------- /she.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define FG TB_DEFAULT 16 | #define BG TB_DEFAULT 17 | #define PGSIZ 16 18 | 19 | enum { 20 | HEX, 21 | ASCII, 22 | }; 23 | 24 | typedef struct { 25 | int id; 26 | char str[BUFSIZ]; 27 | } table_t; 28 | 29 | typedef struct { 30 | char *map; 31 | int d; 32 | off_t siz; 33 | off_t off; 34 | off_t csr; 35 | int mode; 36 | char *orig; 37 | int insert; 38 | int status; 39 | int has_err; 40 | } he_t; 41 | 42 | static he_t he; 43 | 44 | enum { 45 | COMMAND_NONE, 46 | COMMAND_QUIT, 47 | COMMAND_SAVE, 48 | COMMAND_SAVEQUIT, 49 | COMMAND_FORCEQUIT, 50 | COMMAND_REDRAW, 51 | COMMAND_HELP, 52 | }; 53 | 54 | table_t commands[] = { 55 | { COMMAND_NONE, "" }, 56 | { COMMAND_QUIT, "q" }, 57 | { COMMAND_SAVE, "w" }, 58 | { COMMAND_SAVEQUIT, "wq" }, 59 | { COMMAND_FORCEQUIT, "q!" }, 60 | { COMMAND_REDRAW, "redraw" }, 61 | { COMMAND_HELP, "help" }, 62 | }; 63 | 64 | int 65 | is_modified() { 66 | int change, i; 67 | 68 | for (change = i = 0; i < he.siz; i++) { 69 | if (he.map[i] != he.orig[i]) { 70 | change = 1; 71 | break; 72 | } 73 | } 74 | 75 | return change; 76 | 77 | } 78 | 79 | void 80 | save() { 81 | lseek(he.d, 0, 0); 82 | write(he.d, he.map, he.siz); 83 | memcpy(he.orig, he.map, he.siz); 84 | } 85 | 86 | void 87 | cleanup(void) { 88 | tb_shutdown(); 89 | munmap(he.map, he.siz); 90 | close(he.d); 91 | 92 | if (he.has_err && errno) 93 | warn("error"); 94 | 95 | exit(0); 96 | } 97 | 98 | size_t 99 | search(const void *l, size_t l_len, const void *s, size_t s_len) 100 | { 101 | size_t i; 102 | 103 | for (i = 0; i < l_len - s_len; i++) 104 | if (memcmp(l + i, s, s_len) == 0) 105 | return i; 106 | 107 | return 0; 108 | } 109 | 110 | void 111 | tb_print(char *str, int x, int y, int fg, int bg, int len) { 112 | int n = 0; 113 | 114 | while (*str) { 115 | uint32_t utf; 116 | 117 | str += tb_utf8_char_to_unicode(&utf, str); 118 | tb_change_cell(x, y, utf, fg, bg); 119 | x++; 120 | 121 | if (++n == len) 122 | return; 123 | } 124 | } 125 | 126 | void 127 | tb_printf(int x, int y, int fg, int bg, const char *fmt, ...) { 128 | char buf[256]; 129 | size_t n; 130 | 131 | va_list args; 132 | va_start(args, fmt); 133 | vsnprintf(buf,256, fmt, args); 134 | va_end(args); 135 | 136 | n = strnlen(buf, 256); 137 | tb_print(buf, x, y, fg, bg, n); 138 | } 139 | 140 | void 141 | redraw(void) { 142 | int y, x, z, j, i; 143 | int fg, bg; 144 | 145 | tb_clear(); 146 | 147 | for (y = 0, i = he.off; y < tb_height() - 1; y++) { 148 | /* line numbers */ 149 | if (i < he.siz) 150 | tb_printf(0, y, FG, BG, "%08x", he.off + (y * 16)); 151 | else 152 | tb_printf(0, y, 239, BG, "~"); 153 | 154 | for (x = 0; x < 4; x++) { 155 | for (z = 0; z < 4; z++, i++) { 156 | fg = he.map[i] == he.orig[i] ? FG : 196; 157 | 158 | /* hex chars */ 159 | tb_printf(13 + (x * 12) + (z * 3), y, 160 | i < he.siz ? fg : 239, BG, "%02x", 161 | i > he.siz ? 0 : 162 | (unsigned char)he.map[i]); 163 | 164 | /* ascii */ 165 | tb_printf(64 + i % 16, y, i >= he.siz ? 239 166 | : fg, BG, "%c", isprint(he.map[i]) 167 | ? he.map[i] : '.'); 168 | 169 | /* focused */ 170 | if (he.csr == i) { 171 | tb_printf(13 + (x * 12) + (z * 3), y, 172 | 16, he.mode == HEX ? 226 : 255, 173 | "%02x", 174 | (unsigned char)he.map[i]); 175 | tb_printf(64 + i % 16, y, 16, 176 | he.mode == ASCII ? 226 : 225, 177 | "%c", 178 | isprint(he.map[i]) ? he.map[i] 179 | : '.'); 180 | } 181 | } 182 | } 183 | } 184 | 185 | /* status bar */ 186 | if (!he.status) { 187 | tb_printf(0, tb_height() - 1, FG, BG, "%s%s ^C quit, ^X save", 188 | he.insert ? "INSERT " : "", 189 | he.mode == ASCII ? "ascii" : "hex"); 190 | tb_present(); 191 | } 192 | he.status = 0; 193 | } 194 | 195 | int 196 | isxdigits(char *str) { 197 | char *p = str; 198 | 199 | while (*p) { 200 | if (isxdigit(*p)) 201 | *p++; 202 | else 203 | return 0; 204 | } 205 | 206 | return 1; 207 | } 208 | 209 | void 210 | scroll(uint16_t key) { 211 | int i; 212 | 213 | switch (key) { 214 | case 'k': 215 | case TB_KEY_ARROW_UP: 216 | if (he.csr >= 16) { 217 | if ((he.csr - he.off) / 16 == 0) 218 | he.off -= 16; 219 | 220 | he.csr -= 16; 221 | } 222 | break; 223 | case 'j': 224 | case TB_KEY_ARROW_DOWN: 225 | if ((he.csr - he.off) / 16 == tb_height() - 2) 226 | he.off += 16; 227 | 228 | if (he.csr < he.siz - 16) 229 | he.csr += 16; 230 | break; 231 | case 'l': 232 | case TB_KEY_ARROW_RIGHT: 233 | if ((he.csr - he.off) / 16 == tb_height() - 2) 234 | if ((he.csr % 16) == 15) 235 | he.off += 16; 236 | 237 | if (he.csr < he.siz - 1) 238 | he.csr += 1; 239 | break; 240 | case 'h': 241 | case TB_KEY_ARROW_LEFT: 242 | if (he.csr > 0) 243 | he.csr -= 1; 244 | break; 245 | case TB_KEY_END: 246 | for (i = 0; i < he.siz - 1 / 16; i++) 247 | scroll(TB_KEY_ARROW_DOWN); 248 | for (i = 0; i < he.siz - 1 % 16; i++) 249 | scroll(TB_KEY_ARROW_RIGHT); 250 | break; 251 | case TB_KEY_HOME: 252 | he.off = 0; 253 | he.csr = 0; 254 | break; 255 | } 256 | } 257 | 258 | int 259 | lookup_cmd(char *cmd) { 260 | int i; 261 | 262 | for (i = 0; i < sizeof(commands)/sizeof(*commands); i++) 263 | if (!strcmp(cmd, commands[i].str)) 264 | return commands[i].id; 265 | 266 | return COMMAND_NONE; 267 | } 268 | 269 | void 270 | runcmd(char *cmd) { 271 | struct tb_event ev; 272 | ssize_t offset; 273 | int i; 274 | 275 | switch (lookup_cmd(cmd)) { 276 | case COMMAND_REDRAW: 277 | redraw(); 278 | break; 279 | case COMMAND_SAVE: 280 | save(); 281 | break; 282 | case COMMAND_SAVEQUIT: 283 | save(); 284 | case COMMAND_QUIT: 285 | if (is_modified()) { 286 | tb_printf(0, tb_height() - 1, FG, BG, 287 | "you have unsaved changes, press ^C to quit without saving"); 288 | tb_present(); 289 | if (tb_poll_event(&ev) != TB_EVENT_KEY) 290 | break; 291 | if (ev.key == TB_KEY_CTRL_C) 292 | cleanup(); 293 | } else 294 | cleanup(); 295 | break; 296 | case COMMAND_FORCEQUIT: 297 | cleanup(); 298 | break; 299 | default: 300 | if (isxdigits(cmd)) { 301 | offset = strtoul(cmd, NULL, 16); 302 | 303 | scroll(TB_KEY_HOME); 304 | for (i = 0; i < offset / 16; i++) 305 | scroll(TB_KEY_ARROW_DOWN); 306 | for (i = 0; i < offset % 16; i++) 307 | scroll(TB_KEY_ARROW_RIGHT); 308 | } 309 | break; 310 | } 311 | 312 | return; 313 | } 314 | 315 | int 316 | main(int argc, char **argv) { 317 | struct tb_event ev; 318 | int i, change; 319 | char needle[256]; 320 | 321 | setlocale(LC_ALL, ""); 322 | tb_init(); 323 | tb_select_output_mode(TB_OUTPUT_256); 324 | 325 | /* check args */ 326 | he.d = open(argv[1], O_RDWR); 327 | if (he.d < 0) 328 | goto error; 329 | 330 | he.siz = lseek(he.d, 0, SEEK_END); 331 | if (he.siz < 0) 332 | goto error; 333 | 334 | if (he.siz < 1) { 335 | errno = EIO; 336 | goto error; 337 | } 338 | 339 | he.map = mmap(0, he.siz, PROT_READ | PROT_WRITE, MAP_PRIVATE, he.d, 0); 340 | if (he.map == MAP_FAILED) 341 | goto error; 342 | 343 | he.orig = malloc(sizeof(char) * he.siz); 344 | if (he.orig == NULL) 345 | goto error; 346 | 347 | memcpy(he.orig, he.map, he.siz); 348 | 349 | he.off = he.csr = 0; 350 | he.mode = HEX; 351 | redraw(); 352 | 353 | char *p = needle; 354 | size_t base; 355 | while (tb_poll_event(&ev) > 0) { 356 | switch (ev.type) { 357 | case TB_EVENT_KEY: 358 | switch (ev.key) { 359 | case TB_KEY_TAB: 360 | he.mode = he.mode == ASCII ? HEX : ASCII; 361 | break; 362 | case TB_KEY_CTRL_C: 363 | if (is_modified()) { 364 | tb_printf(0, tb_height() - 1, FG, BG, 365 | "you have unsaved changes, press ^C again to quit"); 366 | tb_present(); 367 | if (tb_poll_event(&ev) != TB_EVENT_KEY) 368 | break; 369 | if (ev.key == TB_KEY_CTRL_C) 370 | cleanup(); 371 | } else 372 | cleanup(); 373 | /* NOTREACHED */ 374 | case TB_KEY_CTRL_L: 375 | redraw(); 376 | break; 377 | case TB_KEY_CTRL_X: 378 | save(); 379 | break; 380 | case TB_KEY_CTRL_D: 381 | case TB_KEY_PGDN: 382 | for (i = 0; i < PGSIZ; i++) 383 | scroll(TB_KEY_ARROW_DOWN); 384 | break; 385 | case TB_KEY_CTRL_U: 386 | case TB_KEY_PGUP: 387 | for (i = 0; i < PGSIZ; i++) 388 | scroll(TB_KEY_ARROW_UP); 389 | break; 390 | case TB_KEY_HOME: 391 | scroll(ev.key); 392 | break; 393 | case TB_KEY_END: 394 | for (i = 0; i < he.siz - 1 / 16; i++) 395 | scroll(TB_KEY_ARROW_DOWN); 396 | break; 397 | case TB_KEY_ARROW_UP: 398 | case TB_KEY_ARROW_DOWN: 399 | case TB_KEY_ARROW_RIGHT: 400 | case TB_KEY_ARROW_LEFT: 401 | scroll(ev.key); 402 | break; 403 | case TB_KEY_ESC: 404 | he.insert = 0; 405 | break; 406 | default: 407 | if (he.insert) { 408 | if (he.mode == HEX) { 409 | switch(ev.ch) { 410 | case 'a': case 'A': 411 | case 'b': case 'B': 412 | case 'c': case 'C': 413 | case 'd': case 'D': 414 | case 'e': case 'E': 415 | case 'f': case 'F': 416 | case '0': 417 | case '1': 418 | case '2': 419 | case '3': 420 | case '4': 421 | case '5': 422 | case '6': 423 | case '7': 424 | case '8': 425 | case '9': { 426 | unsigned char s1, s2; 427 | char p[3] = { 0, 0, 0 }; 428 | 429 | s1 = ev.ch; 430 | tb_printf(13 + (he.csr % 16) * 3, 431 | (he.csr - he.off) / 16, 432 | 16, 255, "%c_", ev.ch); 433 | tb_present(); 434 | if (tb_poll_event(&ev) != TB_EVENT_KEY) 435 | break; 436 | 437 | s2 = ev.ch; 438 | p[0] = s1; 439 | p[1] = s2; 440 | he.map[he.csr] = (unsigned char) 441 | strtoul(p, NULL, 16); 442 | scroll(TB_KEY_ARROW_RIGHT); 443 | /* FALLTHROUGH */ 444 | } break; 445 | } 446 | } else if (he.mode == ASCII) { 447 | he.map[he.csr] = ev.ch; 448 | scroll(TB_KEY_ARROW_RIGHT); 449 | } 450 | break; 451 | } 452 | 453 | switch (ev.ch) { 454 | case ':': { 455 | /* command */ 456 | char cmdbuf[BUFSIZ]; 457 | char *p = cmdbuf; 458 | int b = 0; 459 | 460 | memset(p, 0, BUFSIZ); 461 | tb_printf(0, tb_height() - 1, 462 | 16, 255, "%-*c", 463 | tb_width(), ':'); 464 | tb_present(); 465 | 466 | he.status = 1; 467 | redraw(); 468 | 469 | while (ev.key != TB_KEY_ESC) { 470 | tb_poll_event(&ev); 471 | switch(ev.key) { 472 | case TB_KEY_BACKSPACE: 473 | case TB_KEY_BACKSPACE2: 474 | *(--p) = 0; 475 | break; 476 | case TB_KEY_ENTER: 477 | /* run command */ 478 | runcmd(cmdbuf); 479 | b = 1; 480 | break; 481 | default: 482 | *p++ = ev.ch; 483 | } 484 | if (b > 0) 485 | break; 486 | 487 | tb_printf(0, tb_height() - 1, 488 | 16, 255, ":%-*s", 489 | tb_width() - 1, cmdbuf); 490 | tb_present(); 491 | } 492 | 493 | he.status = 0; 494 | 495 | } break; 496 | case 'h': 497 | case 'j': 498 | case 'k': 499 | case 'l': 500 | scroll(ev.ch); 501 | break; 502 | case 'g': 503 | if (tb_poll_event(&ev) != TB_EVENT_KEY) 504 | break; 505 | 506 | if (ev.ch == 'g') 507 | scroll(TB_KEY_HOME); 508 | break; 509 | case 'G': 510 | scroll(TB_KEY_END); 511 | break; 512 | case 'i': 513 | he.insert = 1; 514 | break; 515 | case 'n': 516 | base = search(he.map + he.csr + 1, 517 | (he.siz - he.csr) - 1, needle, 518 | (p - needle) - 1); 519 | 520 | if (base == 0) { 521 | tb_printf(0, tb_height() - 1, 522 | 16, 196, "not found"); 523 | tb_present(); 524 | he.status = 1; 525 | } else { 526 | base += he.csr + 1; 527 | scroll(TB_KEY_HOME); 528 | he.off = base / 16; 529 | for (i = 0; i < he.off; i++) 530 | scroll(TB_KEY_ARROW_DOWN); 531 | he.off = (base / 16) * 16; 532 | he.csr = base; 533 | redraw(); 534 | } 535 | break; 536 | case '/': { 537 | memset(needle, 0, 256); 538 | p = needle; 539 | 540 | while (ev.key != TB_KEY_ENTER) { 541 | tb_poll_event(&ev); 542 | 543 | switch(ev.key) { 544 | case TB_KEY_BACKSPACE: 545 | case TB_KEY_BACKSPACE2: 546 | *(--p) = 0; 547 | break; 548 | default: 549 | if ((he.mode == HEX) && (!isxdigit(ev.ch))) 550 | continue; 551 | *p++ = ev.ch; 552 | } 553 | tb_printf(0, tb_height() - 1, 554 | FG, BG, "/%-*s", 555 | tb_width(), needle); 556 | tb_present(); 557 | } 558 | 559 | if (he.mode == HEX) { 560 | unsigned int len = p - needle; 561 | char buf[256]; 562 | char q[3] = { 0, 0, 0 }; 563 | char *b = buf; 564 | memcpy(buf, needle, 256); 565 | memset(needle, 0, 256); 566 | p = needle; 567 | 568 | if ((len % 2) != 0) { 569 | q[0] = '0'; 570 | q[1] = *b++; 571 | *p++ = strtoul(q, NULL, 16); 572 | } 573 | 574 | while ((b - buf) < len) { 575 | q[0] = *b++; 576 | q[1] = *b++; 577 | *p++ = strtoul(q, NULL, 16); 578 | } 579 | 580 | *p++ = 0; 581 | } 582 | 583 | base = search(he.map, he.siz, needle, 584 | (p - needle) - 1); 585 | if (base == 0) { 586 | tb_printf(0, tb_height() - 1, 587 | 16, 196, "not found"); 588 | tb_present(); 589 | he.status = 1; 590 | } else { 591 | scroll(TB_KEY_HOME); 592 | he.off = base / 16; 593 | for (i = 0; i < he.off; i++) 594 | scroll(TB_KEY_ARROW_DOWN); 595 | he.off = (base / 16) * 16; 596 | he.csr = base; 597 | redraw(); 598 | } 599 | } break; 600 | } 601 | 602 | } 603 | case TB_EVENT_RESIZE: 604 | redraw(); 605 | } 606 | } 607 | 608 | error: 609 | he.has_err = 1; 610 | cleanup(); 611 | return 0; 612 | } 613 | 614 | --------------------------------------------------------------------------------