File: nh.c 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 You can build this command-line app by running 27 28 cc -Wall -s -O3 -march=native -mtune=native -flto -o ./nh ./nh.c 29 30 Building with COMPACT_OUTPUT defined makes `nh` output many fewer bytes, at 31 the cost of using arguably worse colors. You can do that by running 32 33 cc -s -O3 -march=native -mtune=native -flto -D COMPACT_OUTPUT -o ./nh ./nh.c 34 35 Building for macos always uses COMPACT_OUTPUT, as the default terminal app 36 there still doesn't support rgb colors. 37 */ 38 39 #include <stdbool.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <sys/stat.h> 44 45 #ifdef _WIN32 46 #include <fcntl.h> 47 #include <windows.h> 48 #endif 49 50 #ifdef RED_ERRORS 51 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 52 #ifdef __APPLE__ 53 #define ERROR_STYLE "\x1b[31m" 54 #endif 55 #define RESET_STYLE "\x1b[0m" 56 #else 57 #define ERROR_STYLE 58 #define RESET_STYLE 59 #endif 60 61 #ifdef __APPLE__ 62 #define COMPACT_OUTPUT 63 #endif 64 65 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 66 67 #ifndef IBUF_SIZE 68 #define IBUF_SIZE (32 * 1024) 69 #endif 70 71 // EMIT_CONST emits string constants without their final null byte 72 #define EMIT_CONST(w, x) fwrite(x, 1, sizeof(x) - 1, w) 73 74 const char* info = "" 75 "nh [options...] [filenames...]\n" 76 "\n" 77 "Nice Hexadecimal is a simple hexadecimal (base-16) viewer to inspect bytes\n" 78 "from files or standard input.\n" 79 "\n" 80 "Each line shows the starting offset for the bytes shown, 16 of the bytes\n" 81 "themselves in base-16 notation, and any ASCII codes when the byte values\n" 82 "are in the typical ASCII range.\n" 83 "\n" 84 "The base-16 codes are color-coded, with most bytes shown in gray, while\n" 85 "all-1 and all-0 bytes are shown in orange and blue respectively.\n" 86 "\n" 87 "All-0 bytes are the commonest kind in most binary file types and, along\n" 88 "with all-1 bytes are also a special case worth noticing when exploring\n" 89 "binary data, so it makes sense for them to stand out right away.\n" 90 "\n" 91 "\n" 92 "Options\n" 93 "\n" 94 " -h, --h show this help message\n" 95 " -help, --help aliases for option -h\n" 96 "\n" 97 " -p, --p plain-text output, without ANSI styles\n" 98 " -plain, --plain aliases for option -p\n" 99 "\n" 100 " -do, --do show decimal (base-10) offsets, instead of hex ones\n" 101 " -dec, --dec aliases for option -do\n" 102 " -ho, --ho show hex (base-16) offsets, instead of base-10 ones\n" 103 " -hex, --hex aliases for option -ho\n" 104 ""; 105 106 #ifdef COMPACT_OUTPUT 107 #define OUTPUT_FOR_00 "\x1b[36m00 " 108 #define OUTPUT_FOR_FF "\x1b[35mff " 109 #define NORMAL_HEX_STYLE "\x1b[37m" 110 #define ASCII_HEX_STYLE "\x1b[32m" 111 #define ASCII_BYTE_STYLE "\x1b[30m" 112 #define ASCII_WS_STYLE "\x1b[33m" 113 #else 114 #define OUTPUT_FOR_00 "\x1b[38;2;135;175;255m00 " 115 #define OUTPUT_FOR_FF "\x1b[38;2;255;135;95mff " 116 #define NORMAL_HEX_STYLE "\x1b[38;2;148;148;148m" 117 #define ASCII_HEX_STYLE "\x1b[38;2;102;175;135m" 118 #define ASCII_BYTE_STYLE "\x1b[38;2;78;78;78m" 119 #define ASCII_WS_STYLE "\x1b[38;2;6;152;154m" 120 #endif 121 122 // write_hex is faster than calling fprintf(w, "%02x", b): this matters 123 // because it's called for every input byte 124 static inline void write_hex(FILE* w, unsigned char b) { 125 const char* hex_digits = "0123456789abcdef"; 126 fputc(hex_digits[b >> 4], w); 127 fputc(hex_digits[b & 0x0f], w); 128 } 129 130 // write_styled_hex emits an ANSI color-coded hexadecimal representation of 131 // the byte given; the int given/returned is to keep track of the type of 132 // color-coding used in the previous call, to save some output bytes when 133 // emitting multiple same-styled bytes in sequence, in case the specific 134 // same-styled run allows it 135 static inline int write_styled_hex(FILE* w, unsigned char b, int prev) { 136 // regular ASCII display symbols 137 if (33 <= b && b <= 126) { 138 EMIT_CONST(w, ASCII_HEX_STYLE); 139 write_hex(w, b); 140 EMIT_CONST(w, ASCII_BYTE_STYLE); 141 fputc(b, w); 142 return -1; 143 } 144 145 // all-bits-off is almost always noteworthy 146 if (b == 0) { 147 if (prev != 0) { 148 EMIT_CONST(w, OUTPUT_FOR_00); 149 } else { 150 EMIT_CONST(w, "00 "); 151 } 152 return 0; 153 } 154 // all-bits-on is often noteworthy 155 if (b == 0xff) { 156 if (prev != 255) { 157 EMIT_CONST(w, OUTPUT_FOR_FF); 158 } else { 159 EMIT_CONST(w, "ff "); 160 } 161 return 255; 162 } 163 164 // ASCII whitespace 165 if (b == ' ' || b == '\n' || b == '\t' || b == '\r') { 166 EMIT_CONST(w, ASCII_WS_STYLE); 167 write_hex(w, b); 168 EMIT_CONST(w, ASCII_BYTE_STYLE " "); 169 return -1; 170 } 171 172 // ASCII control values, and other bytes beyond displayable ASCII 173 if (prev != 10) { 174 EMIT_CONST(w, NORMAL_HEX_STYLE); 175 } 176 write_hex(w, b); 177 fputc(' ', w); 178 return 10; 179 } 180 181 // ruler emits a ruler-like string of spaced-out symbols 182 static inline void ruler(FILE* w, size_t bytes_per_line) { 183 const size_t gap = 4; 184 if (bytes_per_line < gap) { 185 return; 186 } 187 188 EMIT_CONST(w, " ·"); 189 for (size_t n = bytes_per_line - gap; n >= gap; n -= gap) { 190 EMIT_CONST(w, " ·"); 191 } 192 } 193 194 // write_commas_uint shows a number by separating 3-digits groups with commas 195 void write_commas_uint(FILE* w, size_t n) { 196 if (n == 0) { 197 EMIT_CONST(w, "0"); 198 return; 199 } 200 201 size_t digits; 202 // 20 is the most digits unsigned 64-bit ints can ever need 203 unsigned char buf[24]; 204 for (digits = 0; n > 0; digits++, n /= 10) { 205 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 206 } 207 208 // now emit the leading digits, which may not come in 3 209 size_t leading = digits % 3; 210 if (leading == 0) { 211 // avoid having a comma before the first digit 212 leading = digits < 3 ? digits : 3; 213 } 214 unsigned char* start = buf + sizeof(buf) - digits; 215 fwrite(start, 1, leading, w); 216 start += leading; 217 digits -= leading; 218 219 // now emit all remaining digits in groups of 3, alternating styles 220 for (; digits > 0; start += 3, digits -= 3) { 221 fputc(',', w); 222 fwrite(start, 1, 3, w); 223 } 224 } 225 226 // output_state ties all values representing the current state shared across 227 // all functions involved in interpreting the input-buffer and showing its 228 // bytes and ASCII values 229 typedef struct output_state { 230 // the whole input-buffer and its currently-used length in bytes 231 unsigned char* buf; 232 size_t buflen; 233 234 // the ASCII-text buffer and its currently-used length in bytes 235 unsigned char* txt; 236 size_t txtlen; 237 238 // offset is the byte counter, shown at the start of each line 239 size_t offset; 240 241 // line_width is how many bytes each line can show at most 242 size_t line_width; 243 244 // lines is the line counter, which is used to provide periodic 245 // breather lines, to make eye-scanning big output blobs easier 246 size_t lines; 247 248 // emit_offset is chosen to emit the offset at the start of each line 249 void (*emit_offset)(FILE* w, size_t offset); 250 251 // showtxt is a hint on whether it's sensible to show the ASCII-text 252 // buffer for the current line 253 bool showtxt; 254 } output_state; 255 256 // peek_ascii looks 2 lines ahead in the buffer to get all ASCII-like runs 257 // of bytes, which are later meant to show on the side panel 258 void peek_ascii(size_t i, size_t end, output_state* os) { 259 os->txtlen = 0; 260 os->showtxt = false; 261 262 for (size_t j = i; j < end; j++) { 263 const unsigned char b = os->buf[j]; 264 const bool is_vis_ascii = ' ' <= b && b <= '~'; 265 os->showtxt = os->showtxt | is_vis_ascii; 266 os->txt[os->txtlen] = is_vis_ascii ? b : ' '; 267 os->txtlen++; 268 } 269 270 // keep trailing spaces in the ASCII viewer, since real ASCII streaks 271 // can have them: an example is the `WAVEfmt ` header in .wav files 272 273 // while (os->txtlen > 0 && os->txt[os->txtlen - 1] == ' ') { 274 // os->txtlen--; 275 // } 276 } 277 278 // write_plain_uint is the unstyled counterpart of func write_styled_uint 279 void write_plain_uint(FILE* w, size_t n) { 280 if (n < 1) { 281 EMIT_CONST(w, " 0"); 282 return; 283 } 284 285 size_t digits; 286 // 20 is the most digits unsigned 64-bit ints can ever need 287 unsigned char buf[24]; 288 for (digits = 0; n > 0; digits++, n /= 10) { 289 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 290 } 291 292 // left-pad the coming digits up to 8 chars 293 if (digits < 8) { 294 fwrite((unsigned char*)" ", 1, 8 - digits, w); 295 } 296 297 // emit all digits 298 const unsigned char* start = buf + sizeof(buf) - digits; 299 fwrite(start, 1, digits, w); 300 } 301 302 void write_hex_uint(FILE* w, size_t n) { 303 if (n < 1) { 304 EMIT_CONST(w, "00000000"); 305 return; 306 } 307 308 size_t digits; 309 // 20 is the most digits unsigned 64-bit ints can ever need 310 unsigned char buf[24]; 311 for (digits = 0; n > 0; digits += 2, n /= 256) { 312 unsigned char b = n % 256; 313 const char* hex_digits = "0123456789abcdef"; 314 buf[sizeof(buf) - 1 - digits - 1] = hex_digits[b >> 4]; 315 buf[sizeof(buf) - 1 - digits - 0] = hex_digits[b & 0x0f]; 316 } 317 318 // left-pad the coming digits up to 8 chars 319 if (digits < 8) { 320 fwrite((unsigned char*)"00000000", 1, 8 - digits, w); 321 } 322 323 // emit all digits 324 const unsigned char* start = buf + sizeof(buf) - digits; 325 fwrite(start, 1, digits, w); 326 } 327 328 // write_styled_uint is a quick way to emit the offset-counter showing at the 329 // start of each line; it assumes 8-item left-padding of values, unless the 330 // numbers are too big for that 331 void write_styled_uint(FILE* w, size_t n) { 332 if (n < 1) { 333 EMIT_CONST(w, " 0"); 334 return; 335 } 336 337 size_t digits; 338 // 20 is the most digits unsigned 64-bit ints can ever need 339 unsigned char buf[24]; 340 for (digits = 0; n > 0; digits++, n /= 10) { 341 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 342 } 343 344 // left-pad the coming digits up to 8 chars 345 if (digits < 8) { 346 fwrite((unsigned char*)" ", 1, 8 - digits, w); 347 } 348 349 // now emit the leading digits, which may be fewer than 3 350 size_t leading = digits % 3; 351 unsigned char* start = buf + sizeof(buf) - digits; 352 fwrite(start, 1, leading, w); 353 start += leading; 354 digits -= leading; 355 356 // now emit all remaining digits in groups of 3, alternating styles 357 bool styled = leading != 0; 358 for (; digits > 0; start += 3, digits -= 3, styled = !styled) { 359 if (styled) { 360 #ifdef COMPACT_OUTPUT 361 EMIT_CONST(w, "\x1b[38;5;248m"); 362 #else 363 EMIT_CONST(w, "\x1b[38;2;168;168;168m"); 364 #endif 365 fwrite(start, 1, 3, w); 366 EMIT_CONST(w, "\x1b[0m"); 367 } else { 368 fwrite(start, 1, 3, w); 369 } 370 } 371 } 372 373 // emit_styled_file_info emits an ANSI-styled line showing a filename and the 374 // file's size in bytes 375 void emit_styled_file_info(FILE* w, const char* path, size_t nbytes) { 376 EMIT_CONST(w, "• "); 377 fwrite((unsigned char*)path, 1, strlen(path), w); 378 #ifdef COMPACT_OUTPUT 379 EMIT_CONST(w, " \x1b[38;5;245m("); 380 #else 381 EMIT_CONST(w, " \x1b[38;2;138;138;138m("); 382 #endif 383 write_commas_uint(w, nbytes); 384 EMIT_CONST(w, " bytes)\x1b[0m\n"); 385 } 386 387 // emit_plain_file_info is the unstyled counterpart of func emit_styled_file_info 388 void emit_plain_file_info(FILE* w, const char* path, size_t nbytes) { 389 EMIT_CONST(w, "• "); 390 fwrite((unsigned char*)path, 1, strlen(path), w); 391 EMIT_CONST(w, " ("); 392 write_commas_uint(w, nbytes); 393 EMIT_CONST(w, " bytes)\n"); 394 } 395 396 // emit_styled_line handles the details of showing a styled line out of the 397 // current input-buffer chunk 398 void emit_styled_line(FILE* w, size_t i, size_t end, output_state* os) { 399 int prev_type = -1; 400 401 for (size_t j = i; j < end; j++, os->offset++) { 402 const unsigned char b = os->buf[j]; 403 404 if (j % os->line_width == 0) { 405 // show a ruler every few lines to make eye-scanning easier 406 if (os->lines % 5 == 0 && os->lines > 0) { 407 #ifdef COMPACT_OUTPUT 408 EMIT_CONST(w, " \x1b[38;5;245m"); 409 #else 410 EMIT_CONST(w, " \x1b[38;2;138;138;138m"); 411 #endif 412 ruler(w, os->line_width); 413 EMIT_CONST(w, "\x1b[0m\n"); 414 } 415 os->lines++; 416 prev_type = -1; // reset previous-style, since it's a new line 417 418 // start next line with offset of its 1st item, also changing the 419 // background color for the colored hex code which will follow 420 // fprintf(stdout, "%8d", os->offset); 421 422 os->emit_offset(w, os->offset); 423 #ifdef COMPACT_OUTPUT 424 // EMIT_CONST(w, " \x1b[48;5;254m"); 425 EMIT_CONST(w, " \x1b[48;5;248m"); 426 #else 427 EMIT_CONST(w, " \x1b[48;2;228;228;228m"); 428 #endif 429 } 430 431 // show the current byte `with style` 432 prev_type = write_styled_hex(w, b, prev_type); 433 } 434 435 if (os->showtxt) { 436 EMIT_CONST(w, "\x1b[0m "); 437 for (size_t j = end - i; j < os->line_width; j++) { 438 EMIT_CONST(w, " "); 439 } 440 441 fwrite(os->txt, 1, os->txtlen, w); 442 fputc('\n', w); 443 return; 444 } 445 446 EMIT_CONST(w, "\x1b[0m\n"); 447 } 448 449 // emit_plain_line handles the details of showing a plain (unstyled) line out 450 // of the current input-buffer chunk 451 void emit_plain_line(FILE* w, size_t i, size_t end, output_state* os) { 452 for (size_t j = i; j < end; j++, os->offset++) { 453 const unsigned char b = os->buf[j]; 454 455 if (j % os->line_width == 0) { 456 // show a ruler every few lines to make eye-scanning easier 457 if (os->lines % 5 == 0 && os->lines > 0) { 458 fputc('\n', w); 459 } 460 os->lines++; 461 462 os->emit_offset(w, os->offset); 463 464 // start next line with offset of its 1st item, also 465 // changing the background color for the colored hex 466 // code which will follow 467 // fprintf(stdout, "%8d", os->offset); 468 EMIT_CONST(w, " "); 469 } 470 471 // show the current byte unstyled 472 write_hex(w, b); 473 fputc(' ', w); 474 } 475 476 if (os->showtxt) { 477 EMIT_CONST(w, " "); 478 for (size_t j = end - i; j < os->line_width; j++) { 479 EMIT_CONST(w, " "); 480 } 481 482 fwrite(os->txt, 1, os->txtlen, w); 483 fputc('\n', w); 484 return; 485 } 486 fputc('\n', w); 487 } 488 489 // config has all the settings used to emit output 490 typedef struct config { 491 // bytes_per_line determines the `width` of output lines 492 size_t bytes_per_line; 493 494 // emit_file_info is chosen to emit file-info with colors or plainly 495 void (*emit_file_info)(FILE* w, const char* path, size_t nbytes); 496 497 // emit_line is chosen to emit hex bytes with colors or plainly 498 void (*emit_line)(FILE* w, size_t i, size_t end, output_state* os); 499 500 // emit_offset is chosen to emit the offset at the start of each line 501 void (*emit_offset)(FILE* w, size_t offset); 502 } config; 503 504 // handle_reader shows all bytes read from the source given as colored hex 505 // values, showing offsets and ASCII symbols on the sides of each output line 506 void handle_reader(FILE* w, FILE* src, config cfg) { 507 // limit line-width to the buffer's capacity 508 if (cfg.bytes_per_line > IBUF_SIZE) { 509 cfg.bytes_per_line = IBUF_SIZE; 510 } 511 512 const size_t two_lines = 2 * cfg.bytes_per_line; 513 unsigned char txt[two_lines]; 514 515 unsigned char buf[IBUF_SIZE]; 516 // ensure the effective buffer-size is a multiple of the line-width 517 size_t max = sizeof(buf) - sizeof(buf) % cfg.bytes_per_line; 518 519 output_state os; 520 os.buf = buf; 521 os.line_width = cfg.bytes_per_line; 522 os.lines = 0; 523 os.offset = 0; 524 os.txt = txt; 525 os.emit_offset = cfg.emit_offset; 526 os.showtxt = true; 527 528 const size_t one_line = cfg.bytes_per_line; 529 530 while (!feof(w)) { 531 os.buflen = fread(&buf, sizeof(buf[0]), max, src); 532 if (os.buflen < 1) { 533 // assume input is over when no bytes were read 534 break; 535 } 536 537 for (size_t i = 0; i < os.buflen; i += one_line) { 538 size_t end; 539 540 // remember all ASCII symbols in current pair of output lines 541 end = i + two_lines < os.buflen ? i + two_lines : os.buflen; 542 peek_ascii(i, end, &os); 543 544 // show current output line 545 end = i + one_line < os.buflen ? i + one_line : os.buflen; 546 cfg.emit_line(w, i, end, &os); 547 } 548 } 549 550 fflush(w); 551 } 552 553 // handle_file handles data from the filename given; returns false only when 554 // the file can't be opened 555 bool handle_file(FILE* w, const char* path, config cfg) { 556 FILE* f = fopen(path, "rb"); 557 if (f == NULL) { 558 fputc('\n', w); 559 fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); 560 return false; 561 } 562 563 // get the file size 564 struct stat st; 565 fstat(fileno(f), &st); 566 567 // show output 568 cfg.emit_file_info(w, path, st.st_size); 569 fputc('\n', w); 570 handle_reader(w, f, cfg); 571 572 fclose(f); 573 return true; 574 } 575 576 // is_help_option simplifies control-flow for func run 577 bool is_help_option(const char* s) { 578 return (s[0] == '-') && ( 579 strcmp(s, "-h") == 0 || 580 strcmp(s, "-help") == 0 || 581 strcmp(s, "--h") == 0 || 582 strcmp(s, "--help") == 0 583 ); 584 } 585 586 // is_plain_option simplifies control-flow for func run 587 bool is_plain_option(const char* s) { 588 return (s[0] == '-') && ( 589 strcmp(s, "-p") == 0 || 590 strcmp(s, "--p") == 0 || 591 strcmp(s, "-plain") == 0 || 592 strcmp(s, "--plain") == 0 593 ); 594 } 595 596 // is_hex_offsets simplifies control-flow for func run 597 bool is_hex_offsets_option(const char* s) { 598 return (s[0] == '-') && ( 599 strcmp(s, "-ho") == 0 || 600 strcmp(s, "--ho") == 0 || 601 strcmp(s, "-hex") == 0 || 602 strcmp(s, "--hex") == 0 || 603 strcmp(s, "-hexoffsets") == 0 || 604 strcmp(s, "--hexoffsets") == 0 || 605 strcmp(s, "-hex-offsets") == 0 || 606 strcmp(s, "--hex-offsets") == 0 607 ); 608 } 609 610 // is_dec_offsets simplifies control-flow for func run 611 bool is_dec_offsets_option(const char* s) { 612 return (s[0] == '-') && ( 613 strcmp(s, "-do") == 0 || 614 strcmp(s, "--do") == 0 || 615 strcmp(s, "-dec") == 0 || 616 strcmp(s, "--dec") == 0 || 617 strcmp(s, "-decoffsets") == 0 || 618 strcmp(s, "--decoffsets") == 0 || 619 strcmp(s, "-dec-offsets") == 0 || 620 strcmp(s, "--dec-offsets") == 0 621 ); 622 } 623 624 // run returns the number of errors 625 int run(int argc, char** argv, FILE* w) { 626 config cfg; 627 cfg.bytes_per_line = 16; 628 cfg.emit_line = &emit_styled_line; 629 cfg.emit_file_info = &emit_styled_file_info; 630 cfg.emit_offset = &write_styled_uint; 631 632 size_t files = 0; 633 size_t errors = 0; 634 635 // handle all filenames/options given 636 for (size_t i = 1; i < argc && !feof(w); i++) { 637 // a `-` filename stands for the standard input 638 if (argv[i][0] == '-' && argv[i][1] == 0) { 639 EMIT_CONST(w, "• <stdin>\n"); 640 EMIT_CONST(w, "\n"); 641 handle_reader(w, stdin, cfg); 642 continue; 643 } 644 645 if (is_plain_option(argv[i])) { 646 cfg.emit_line = &emit_plain_line; 647 cfg.emit_file_info = &emit_plain_file_info; 648 // hex offsets are already plain 649 if (cfg.emit_offset != &write_hex_uint) { 650 cfg.emit_offset = &write_plain_uint; 651 } 652 continue; 653 } 654 655 if (is_hex_offsets_option(argv[i])) { 656 cfg.emit_offset = &write_hex_uint; 657 continue; 658 } 659 660 if (is_dec_offsets_option(argv[i])) { 661 // keep plain decimal offsets that way 662 if (cfg.emit_offset != &write_plain_uint) { 663 cfg.emit_offset = &write_styled_uint; 664 } 665 continue; 666 } 667 668 if (files > 0) { 669 // put an empty line between adjacent hex outputs 670 fputc('\n', w); 671 } 672 673 if (!handle_file(w, argv[i], cfg)) { 674 errors++; 675 } 676 files++; 677 } 678 679 // no filenames means use stdin as the only input 680 if (files == 0) { 681 EMIT_CONST(w, "• <stdin>\n"); 682 EMIT_CONST(w, "\n"); 683 handle_reader(w, stdin, cfg); 684 } 685 686 return errors; 687 } 688 689 int main(int argc, char** argv) { 690 #ifdef _WIN32 691 setmode(fileno(stdin), O_BINARY); 692 // ensure output lines end in LF instead of CRLF on windows 693 setmode(fileno(stdout), O_BINARY); 694 setmode(fileno(stderr), O_BINARY); 695 #endif 696 697 if (argc > 1 && is_help_option(argv[1])) { 698 fprintf(stderr, "%s", info); 699 return 0; 700 } 701 702 // enable full buffering for stdout 703 setvbuf(stdout, NULL, _IOFBF, 0); 704 705 return run(argc, argv, stdout) == 0 ? 0 : 1; 706 }