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[38;5;111m00 " 108 // #define OUTPUT_FOR_FF "\x1b[38;5;209mff " 109 // #define NORMAL_HEX_STYLE "\x1b[38;5;246m" 110 // #define ASCII_HEX_STYLE "\x1b[38;5;72m" 111 // #define ASCII_BYTE_STYLE "\x1b[38;5;239m" 112 #define OUTPUT_FOR_00 "\x1b[34m00 " 113 #define OUTPUT_FOR_FF "\x1b[33mff " 114 // #define NORMAL_HEX_STYLE "\x1b[37m" 115 #define NORMAL_HEX_STYLE "\x1b[38;5;246m" 116 #define ASCII_HEX_STYLE "\x1b[32m" 117 #define ASCII_BYTE_STYLE "\x1b[30m" 118 #define ASCII_WS_STYLE "\x1b[36m" 119 #else 120 #define OUTPUT_FOR_00 "\x1b[38;2;135;175;255m00 " 121 #define OUTPUT_FOR_FF "\x1b[38;2;255;135;95mff " 122 #define NORMAL_HEX_STYLE "\x1b[38;2;148;148;148m" 123 #define ASCII_HEX_STYLE "\x1b[38;2;102;175;135m" 124 #define ASCII_BYTE_STYLE "\x1b[38;2;78;78;78m" 125 #define ASCII_WS_STYLE "\x1b[38;2;6;152;154m" 126 #endif 127 128 // write_hex is faster than calling fprintf(w, "%02x", b): this matters 129 // because it's called for every input byte 130 static inline void write_hex(FILE* w, unsigned char b) { 131 const char* hex_digits = "0123456789abcdef"; 132 fputc(hex_digits[b >> 4], w); 133 fputc(hex_digits[b & 0x0f], w); 134 } 135 136 // write_styled_hex emits an ANSI color-coded hexadecimal representation of 137 // the byte given; the int given/returned is to keep track of the type of 138 // color-coding used in the previous call, to save some output bytes when 139 // emitting multiple same-styled bytes in sequence, in case the specific 140 // same-styled run allows it 141 static inline int write_styled_hex(FILE* w, unsigned char b, int prev) { 142 // regular ASCII display symbols 143 if (33 <= b && b <= 126) { 144 EMIT_CONST(w, ASCII_HEX_STYLE); 145 write_hex(w, b); 146 EMIT_CONST(w, ASCII_BYTE_STYLE); 147 fputc(b, w); 148 return -1; 149 } 150 151 // all-bits-off is almost always noteworthy 152 if (b == 0) { 153 if (prev != 0) { 154 EMIT_CONST(w, OUTPUT_FOR_00); 155 } else { 156 EMIT_CONST(w, "00 "); 157 } 158 return 0; 159 } 160 // all-bits-on is often noteworthy 161 if (b == 0xff) { 162 if (prev != 255) { 163 EMIT_CONST(w, OUTPUT_FOR_FF); 164 } else { 165 EMIT_CONST(w, "ff "); 166 } 167 return 255; 168 } 169 170 // ASCII whitespace 171 if (b == ' ' || b == '\n' || b == '\t' || b == '\r') { 172 EMIT_CONST(w, ASCII_WS_STYLE); 173 write_hex(w, b); 174 EMIT_CONST(w, ASCII_BYTE_STYLE " "); 175 return -1; 176 } 177 178 // ASCII control values, and other bytes beyond displayable ASCII 179 if (prev != 10) { 180 EMIT_CONST(w, NORMAL_HEX_STYLE); 181 } 182 write_hex(w, b); 183 fputc(' ', w); 184 return 10; 185 } 186 187 // ruler emits a ruler-like string of spaced-out symbols 188 static inline void ruler(FILE* w, size_t bytes_per_line) { 189 const size_t gap = 4; 190 if (bytes_per_line < gap) { 191 return; 192 } 193 194 EMIT_CONST(w, " ·"); 195 for (size_t n = bytes_per_line - gap; n >= gap; n -= gap) { 196 EMIT_CONST(w, " ·"); 197 } 198 } 199 200 // write_commas_uint shows a number by separating 3-digits groups with commas 201 void write_commas_uint(FILE* w, size_t n) { 202 if (n == 0) { 203 EMIT_CONST(w, "0"); 204 return; 205 } 206 207 size_t digits; 208 // 20 is the most digits unsigned 64-bit ints can ever need 209 unsigned char buf[24]; 210 for (digits = 0; n > 0; digits++, n /= 10) { 211 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 212 } 213 214 // now emit the leading digits, which may not come in 3 215 size_t leading = digits % 3; 216 if (leading == 0) { 217 // avoid having a comma before the first digit 218 leading = digits < 3 ? digits : 3; 219 } 220 unsigned char* start = buf + sizeof(buf) - digits; 221 fwrite(start, 1, leading, w); 222 start += leading; 223 digits -= leading; 224 225 // now emit all remaining digits in groups of 3, alternating styles 226 for (; digits > 0; start += 3, digits -= 3) { 227 fputc(',', w); 228 fwrite(start, 1, 3, w); 229 } 230 } 231 232 // output_state ties all values representing the current state shared across 233 // all functions involved in interpreting the input-buffer and showing its 234 // bytes and ASCII values 235 typedef struct output_state { 236 // the whole input-buffer and its currently-used length in bytes 237 unsigned char* buf; 238 size_t buflen; 239 240 // the ASCII-text buffer and its currently-used length in bytes 241 unsigned char* txt; 242 size_t txtlen; 243 244 // offset is the byte counter, shown at the start of each line 245 size_t offset; 246 247 // line_width is how many bytes each line can show at most 248 size_t line_width; 249 250 // lines is the line counter, which is used to provide periodic 251 // breather lines, to make eye-scanning big output blobs easier 252 size_t lines; 253 254 // emit_offset is chosen to emit the offset at the start of each line 255 void (*emit_offset)(FILE* w, size_t offset); 256 257 // showtxt is a hint on whether it's sensible to show the ASCII-text 258 // buffer for the current line 259 bool showtxt; 260 } output_state; 261 262 // peek_ascii looks 2 lines ahead in the buffer to get all ASCII-like runs 263 // of bytes, which are later meant to show on the side panel 264 void peek_ascii(size_t i, size_t end, output_state* os) { 265 os->txtlen = 0; 266 os->showtxt = false; 267 268 for (size_t j = i; j < end; j++) { 269 const unsigned char b = os->buf[j]; 270 const bool is_vis_ascii = ' ' <= b && b <= '~'; 271 os->showtxt = os->showtxt | is_vis_ascii; 272 os->txt[os->txtlen] = is_vis_ascii ? b : ' '; 273 os->txtlen++; 274 } 275 276 // keep trailing spaces in the ASCII viewer, since real ASCII streaks 277 // can have them: an example is the `WAVEfmt ` header in .wav files 278 279 // while (os->txtlen > 0 && os->txt[os->txtlen - 1] == ' ') { 280 // os->txtlen--; 281 // } 282 } 283 284 // write_plain_uint is the unstyled counterpart of func write_styled_uint 285 void write_plain_uint(FILE* w, size_t n) { 286 if (n < 1) { 287 EMIT_CONST(w, " 0"); 288 return; 289 } 290 291 size_t digits; 292 // 20 is the most digits unsigned 64-bit ints can ever need 293 unsigned char buf[24]; 294 for (digits = 0; n > 0; digits++, n /= 10) { 295 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 296 } 297 298 // left-pad the coming digits up to 8 chars 299 if (digits < 8) { 300 fwrite((unsigned char*)" ", 1, 8 - digits, w); 301 } 302 303 // emit all digits 304 const unsigned char* start = buf + sizeof(buf) - digits; 305 fwrite(start, 1, digits, w); 306 } 307 308 void write_hex_uint(FILE* w, size_t n) { 309 if (n < 1) { 310 EMIT_CONST(w, "00000000"); 311 return; 312 } 313 314 size_t digits; 315 // 20 is the most digits unsigned 64-bit ints can ever need 316 unsigned char buf[24]; 317 for (digits = 0; n > 0; digits += 2, n /= 256) { 318 unsigned char b = n % 256; 319 const char* hex_digits = "0123456789abcdef"; 320 buf[sizeof(buf) - 1 - digits - 1] = hex_digits[b >> 4]; 321 buf[sizeof(buf) - 1 - digits - 0] = hex_digits[b & 0x0f]; 322 } 323 324 // left-pad the coming digits up to 8 chars 325 if (digits < 8) { 326 fwrite((unsigned char*)"00000000", 1, 8 - digits, w); 327 } 328 329 // emit all digits 330 const unsigned char* start = buf + sizeof(buf) - digits; 331 fwrite(start, 1, digits, w); 332 } 333 334 // write_styled_uint is a quick way to emit the offset-counter showing at the 335 // start of each line; it assumes 8-item left-padding of values, unless the 336 // numbers are too big for that 337 void write_styled_uint(FILE* w, size_t n) { 338 if (n < 1) { 339 EMIT_CONST(w, " 0"); 340 return; 341 } 342 343 size_t digits; 344 // 20 is the most digits unsigned 64-bit ints can ever need 345 unsigned char buf[24]; 346 for (digits = 0; n > 0; digits++, n /= 10) { 347 buf[sizeof(buf) - 1 - digits] = (n % 10) + '0'; 348 } 349 350 // left-pad the coming digits up to 8 chars 351 if (digits < 8) { 352 fwrite((unsigned char*)" ", 1, 8 - digits, w); 353 } 354 355 // now emit the leading digits, which may be fewer than 3 356 size_t leading = digits % 3; 357 unsigned char* start = buf + sizeof(buf) - digits; 358 fwrite(start, 1, leading, w); 359 start += leading; 360 digits -= leading; 361 362 // now emit all remaining digits in groups of 3, alternating styles 363 bool styled = leading != 0; 364 for (; digits > 0; start += 3, digits -= 3, styled = !styled) { 365 if (styled) { 366 #ifdef COMPACT_OUTPUT 367 EMIT_CONST(w, "\x1b[38;5;248m"); 368 #else 369 EMIT_CONST(w, "\x1b[38;2;168;168;168m"); 370 #endif 371 fwrite(start, 1, 3, w); 372 EMIT_CONST(w, "\x1b[0m"); 373 } else { 374 fwrite(start, 1, 3, w); 375 } 376 } 377 } 378 379 // emit_styled_file_info emits an ANSI-styled line showing a filename and the 380 // file's size in bytes 381 void emit_styled_file_info(FILE* w, const char* path, size_t nbytes) { 382 EMIT_CONST(w, "• "); 383 fwrite((unsigned char*)path, 1, strlen(path), w); 384 #ifdef COMPACT_OUTPUT 385 EMIT_CONST(w, " \x1b[38;5;245m("); 386 #else 387 EMIT_CONST(w, " \x1b[38;2;138;138;138m("); 388 #endif 389 write_commas_uint(w, nbytes); 390 EMIT_CONST(w, " bytes)\x1b[0m\n"); 391 } 392 393 // emit_plain_file_info is the unstyled counterpart of func emit_styled_file_info 394 void emit_plain_file_info(FILE* w, const char* path, size_t nbytes) { 395 EMIT_CONST(w, "• "); 396 fwrite((unsigned char*)path, 1, strlen(path), w); 397 EMIT_CONST(w, " ("); 398 write_commas_uint(w, nbytes); 399 EMIT_CONST(w, " bytes)\n"); 400 } 401 402 // emit_styled_line handles the details of showing a styled line out of the 403 // current input-buffer chunk 404 void emit_styled_line(FILE* w, size_t i, size_t end, output_state* os) { 405 int prev_type = -1; 406 407 for (size_t j = i; j < end; j++, os->offset++) { 408 const unsigned char b = os->buf[j]; 409 410 if (j % os->line_width == 0) { 411 // show a ruler every few lines to make eye-scanning easier 412 if (os->lines % 5 == 0 && os->lines > 0) { 413 #ifdef COMPACT_OUTPUT 414 EMIT_CONST(w, " \x1b[38;5;245m"); 415 #else 416 EMIT_CONST(w, " \x1b[38;2;138;138;138m"); 417 #endif 418 ruler(w, os->line_width); 419 EMIT_CONST(w, "\x1b[0m\n"); 420 } 421 os->lines++; 422 prev_type = -1; // reset previous-style, since it's a new line 423 424 // start next line with offset of its 1st item, also changing the 425 // background color for the colored hex code which will follow 426 // fprintf(stdout, "%8d", os->offset); 427 428 os->emit_offset(w, os->offset); 429 #ifdef COMPACT_OUTPUT 430 EMIT_CONST(w, " \x1b[48;5;254m"); 431 #else 432 EMIT_CONST(w, " \x1b[48;2;228;228;228m"); 433 #endif 434 } 435 436 // show the current byte `with style` 437 prev_type = write_styled_hex(w, b, prev_type); 438 } 439 440 if (os->showtxt) { 441 EMIT_CONST(w, "\x1b[0m "); 442 for (size_t j = end - i; j < os->line_width; j++) { 443 EMIT_CONST(w, " "); 444 } 445 446 fwrite(os->txt, 1, os->txtlen, w); 447 fputc('\n', w); 448 return; 449 } 450 451 EMIT_CONST(w, "\x1b[0m\n"); 452 } 453 454 // emit_plain_line handles the details of showing a plain (unstyled) line out 455 // of the current input-buffer chunk 456 void emit_plain_line(FILE* w, size_t i, size_t end, output_state* os) { 457 for (size_t j = i; j < end; j++, os->offset++) { 458 const unsigned char b = os->buf[j]; 459 460 if (j % os->line_width == 0) { 461 // show a ruler every few lines to make eye-scanning easier 462 if (os->lines % 5 == 0 && os->lines > 0) { 463 fputc('\n', w); 464 } 465 os->lines++; 466 467 os->emit_offset(w, os->offset); 468 469 // start next line with offset of its 1st item, also 470 // changing the background color for the colored hex 471 // code which will follow 472 // fprintf(stdout, "%8d", os->offset); 473 EMIT_CONST(w, " "); 474 } 475 476 // show the current byte unstyled 477 write_hex(w, b); 478 fputc(' ', w); 479 } 480 481 if (os->showtxt) { 482 EMIT_CONST(w, " "); 483 for (size_t j = end - i; j < os->line_width; j++) { 484 EMIT_CONST(w, " "); 485 } 486 487 fwrite(os->txt, 1, os->txtlen, w); 488 fputc('\n', w); 489 return; 490 } 491 fputc('\n', w); 492 } 493 494 // config has all the settings used to emit output 495 typedef struct config { 496 // bytes_per_line determines the `width` of output lines 497 size_t bytes_per_line; 498 499 // emit_file_info is chosen to emit file-info with colors or plainly 500 void (*emit_file_info)(FILE* w, const char* path, size_t nbytes); 501 502 // emit_line is chosen to emit hex bytes with colors or plainly 503 void (*emit_line)(FILE* w, size_t i, size_t end, output_state* os); 504 505 // emit_offset is chosen to emit the offset at the start of each line 506 void (*emit_offset)(FILE* w, size_t offset); 507 } config; 508 509 // handle_reader shows all bytes read from the source given as colored hex 510 // values, showing offsets and ASCII symbols on the sides of each output line 511 void handle_reader(FILE* w, FILE* src, config cfg) { 512 // limit line-width to the buffer's capacity 513 if (cfg.bytes_per_line > IBUF_SIZE) { 514 cfg.bytes_per_line = IBUF_SIZE; 515 } 516 517 const size_t two_lines = 2 * cfg.bytes_per_line; 518 unsigned char txt[two_lines]; 519 520 unsigned char buf[IBUF_SIZE]; 521 // ensure the effective buffer-size is a multiple of the line-width 522 size_t max = sizeof(buf) - sizeof(buf) % cfg.bytes_per_line; 523 524 output_state os; 525 os.buf = buf; 526 os.line_width = cfg.bytes_per_line; 527 os.lines = 0; 528 os.offset = 0; 529 os.txt = txt; 530 os.emit_offset = cfg.emit_offset; 531 os.showtxt = true; 532 533 const size_t one_line = cfg.bytes_per_line; 534 535 while (!feof(w)) { 536 os.buflen = fread(&buf, sizeof(buf[0]), max, src); 537 if (os.buflen < 1) { 538 // assume input is over when no bytes were read 539 break; 540 } 541 542 for (size_t i = 0; i < os.buflen; i += one_line) { 543 size_t end; 544 545 // remember all ASCII symbols in current pair of output lines 546 end = i + two_lines < os.buflen ? i + two_lines : os.buflen; 547 peek_ascii(i, end, &os); 548 549 // show current output line 550 end = i + one_line < os.buflen ? i + one_line : os.buflen; 551 cfg.emit_line(w, i, end, &os); 552 } 553 } 554 555 fflush(w); 556 } 557 558 // handle_file handles data from the filename given; returns false only when 559 // the file can't be opened 560 bool handle_file(FILE* w, const char* path, config cfg) { 561 FILE* f = fopen(path, "rb"); 562 if (f == NULL) { 563 fputc('\n', w); 564 fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); 565 return false; 566 } 567 568 // get the file size 569 struct stat st; 570 fstat(fileno(f), &st); 571 572 // show output 573 cfg.emit_file_info(w, path, st.st_size); 574 fputc('\n', w); 575 handle_reader(w, f, cfg); 576 577 fclose(f); 578 return true; 579 } 580 581 // is_help_option simplifies control-flow for func run 582 bool is_help_option(const char* s) { 583 return (s[0] == '-') && ( 584 strcmp(s, "-h") == 0 || 585 strcmp(s, "-help") == 0 || 586 strcmp(s, "--h") == 0 || 587 strcmp(s, "--help") == 0 588 ); 589 } 590 591 // is_plain_option simplifies control-flow for func run 592 bool is_plain_option(const char* s) { 593 return (s[0] == '-') && ( 594 strcmp(s, "-p") == 0 || 595 strcmp(s, "--p") == 0 || 596 strcmp(s, "-plain") == 0 || 597 strcmp(s, "--plain") == 0 598 ); 599 } 600 601 // is_hex_offsets simplifies control-flow for func run 602 bool is_hex_offsets_option(const char* s) { 603 return (s[0] == '-') && ( 604 strcmp(s, "-ho") == 0 || 605 strcmp(s, "--ho") == 0 || 606 strcmp(s, "-hex") == 0 || 607 strcmp(s, "--hex") == 0 || 608 strcmp(s, "-hexoffsets") == 0 || 609 strcmp(s, "--hexoffsets") == 0 || 610 strcmp(s, "-hex-offsets") == 0 || 611 strcmp(s, "--hex-offsets") == 0 612 ); 613 } 614 615 // is_dec_offsets simplifies control-flow for func run 616 bool is_dec_offsets_option(const char* s) { 617 return (s[0] == '-') && ( 618 strcmp(s, "-do") == 0 || 619 strcmp(s, "--do") == 0 || 620 strcmp(s, "-dec") == 0 || 621 strcmp(s, "--dec") == 0 || 622 strcmp(s, "-decoffsets") == 0 || 623 strcmp(s, "--decoffsets") == 0 || 624 strcmp(s, "-dec-offsets") == 0 || 625 strcmp(s, "--dec-offsets") == 0 626 ); 627 } 628 629 // run returns the number of errors 630 int run(int argc, char** argv, FILE* w) { 631 config cfg; 632 cfg.bytes_per_line = 16; 633 cfg.emit_line = &emit_styled_line; 634 cfg.emit_file_info = &emit_styled_file_info; 635 cfg.emit_offset = &write_styled_uint; 636 637 size_t files = 0; 638 size_t errors = 0; 639 640 // handle all filenames/options given 641 for (size_t i = 1; i < argc && !feof(w); i++) { 642 // a `-` filename stands for the standard input 643 if (argv[i][0] == '-' && argv[i][1] == 0) { 644 EMIT_CONST(w, "• <stdin>\n"); 645 EMIT_CONST(w, "\n"); 646 handle_reader(w, stdin, cfg); 647 continue; 648 } 649 650 if (is_plain_option(argv[i])) { 651 cfg.emit_line = &emit_plain_line; 652 cfg.emit_file_info = &emit_plain_file_info; 653 // hex offsets are already plain 654 if (cfg.emit_offset != &write_hex_uint) { 655 cfg.emit_offset = &write_plain_uint; 656 } 657 continue; 658 } 659 660 if (is_hex_offsets_option(argv[i])) { 661 cfg.emit_offset = &write_hex_uint; 662 continue; 663 } 664 665 if (is_dec_offsets_option(argv[i])) { 666 // keep plain decimal offsets that way 667 if (cfg.emit_offset != &write_plain_uint) { 668 cfg.emit_offset = &write_styled_uint; 669 } 670 continue; 671 } 672 673 if (files > 0) { 674 // put an empty line between adjacent hex outputs 675 fputc('\n', w); 676 } 677 678 if (!handle_file(w, argv[i], cfg)) { 679 errors++; 680 } 681 files++; 682 } 683 684 // no filenames means use stdin as the only input 685 if (files == 0) { 686 EMIT_CONST(w, "• <stdin>\n"); 687 EMIT_CONST(w, "\n"); 688 handle_reader(w, stdin, cfg); 689 } 690 691 return errors; 692 } 693 694 int main(int argc, char** argv) { 695 #ifdef _WIN32 696 setmode(fileno(stdin), O_BINARY); 697 // ensure output lines end in LF instead of CRLF on windows 698 setmode(fileno(stdout), O_BINARY); 699 setmode(fileno(stderr), O_BINARY); 700 #endif 701 702 if (argc > 1 && is_help_option(argv[1])) { 703 fprintf(stderr, "%s", info); 704 return 0; 705 } 706 707 // enable full buffering for stdout 708 setvbuf(stdout, NULL, _IOFBF, 0); 709 710 return run(argc, argv, stdout) == 0 ? 0 : 1; 711 }