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