File: chex/data.c
   1 // info is the multi-line help message
   2 const char* info = ""
   3 "chex [options...] [filenames...]\n"
   4 "\n"
   5 "Colored HEXadecimal is a simple hexadecimal (base-16) viewer to inspect\n"
   6 "bytes from files or standard input.\n"
   7 "\n"
   8 "Each line shows the starting offset for the bytes shown, 16 of the bytes\n"
   9 "themselves in base-16 notation, and any ASCII codes when the byte values\n"
  10 "are in the typical ASCII range.\n"
  11 "\n"
  12 "The base-16 codes are color-coded, with most bytes shown in gray, while\n"
  13 "all-1 and all-0 bytes are shown in orange and blue respectively.\n"
  14 "\n"
  15 "All-0 bytes are the commonest kind in most binary file types and, along\n"
  16 "with all-1 bytes are also a special case worth noticing when exploring\n"
  17 "binary data, so it makes sense for them to stand out right away.\n"
  18 "\n"
  19 "\n"
  20 "Options\n"
  21 "\n"
  22 "    -h, --h            show this help message\n"
  23 "    -help, --help      aliases for option -h\n"
  24 "\n"
  25 "    -p, --p            plain-text output, without ANSI styles\n"
  26 "    -plain, --plain    aliases for option -p\n"
  27 "";
  28 
  29 // styled_hex_results is a super-fast direct byte-to-result lookup table, and
  30 // was autogenerated by running the command
  31 //
  32 // seq 0 255 | ./hex-styles.awk
  33 // const char* styled_hex_results[256] = {
  34 //     "\x1b[38;5;111m00 ",
  35 //     "\x1b[38;5;246m01 ",
  36 //     "\x1b[38;5;246m02 ",
  37 //     "\x1b[38;5;246m03 ",
  38 //     "\x1b[38;5;246m04 ",
  39 //     "\x1b[38;5;246m05 ",
  40 //     "\x1b[38;5;246m06 ",
  41 //     "\x1b[38;5;246m07 ",
  42 //     "\x1b[38;5;246m08 ",
  43 //     "\x1b[38;5;246m09 ",
  44 //     "\x1b[38;5;246m0a ",
  45 //     "\x1b[38;5;246m0b ",
  46 //     "\x1b[38;5;246m0c ",
  47 //     "\x1b[38;5;246m0d ",
  48 //     "\x1b[38;5;246m0e ",
  49 //     "\x1b[38;5;246m0f ",
  50 //     "\x1b[38;5;246m10 ",
  51 //     "\x1b[38;5;246m11 ",
  52 //     "\x1b[38;5;246m12 ",
  53 //     "\x1b[38;5;246m13 ",
  54 //     "\x1b[38;5;246m14 ",
  55 //     "\x1b[38;5;246m15 ",
  56 //     "\x1b[38;5;246m16 ",
  57 //     "\x1b[38;5;246m17 ",
  58 //     "\x1b[38;5;246m18 ",
  59 //     "\x1b[38;5;246m19 ",
  60 //     "\x1b[38;5;246m1a ",
  61 //     "\x1b[38;5;246m1b ",
  62 //     "\x1b[38;5;246m1c ",
  63 //     "\x1b[38;5;246m1d ",
  64 //     "\x1b[38;5;246m1e ",
  65 //     "\x1b[38;5;246m1f ",
  66 //     "\x1b[38;5;72m20\x1b[38;5;239m ",
  67 //     "\x1b[38;5;72m21\x1b[38;5;239m!",
  68 //     "\x1b[38;5;72m22\x1b[38;5;239m\"",
  69 //     "\x1b[38;5;72m23\x1b[38;5;239m#",
  70 //     "\x1b[38;5;72m24\x1b[38;5;239m$",
  71 //     "\x1b[38;5;72m25\x1b[38;5;239m%",
  72 //     "\x1b[38;5;72m26\x1b[38;5;239m&",
  73 //     "\x1b[38;5;72m27\x1b[38;5;239m'",
  74 //     "\x1b[38;5;72m28\x1b[38;5;239m(",
  75 //     "\x1b[38;5;72m29\x1b[38;5;239m)",
  76 //     "\x1b[38;5;72m2a\x1b[38;5;239m*",
  77 //     "\x1b[38;5;72m2b\x1b[38;5;239m+",
  78 //     "\x1b[38;5;72m2c\x1b[38;5;239m,",
  79 //     "\x1b[38;5;72m2d\x1b[38;5;239m-",
  80 //     "\x1b[38;5;72m2e\x1b[38;5;239m.",
  81 //     "\x1b[38;5;72m2f\x1b[38;5;239m/",
  82 //     "\x1b[38;5;72m30\x1b[38;5;239m0",
  83 //     "\x1b[38;5;72m31\x1b[38;5;239m1",
  84 //     "\x1b[38;5;72m32\x1b[38;5;239m2",
  85 //     "\x1b[38;5;72m33\x1b[38;5;239m3",
  86 //     "\x1b[38;5;72m34\x1b[38;5;239m4",
  87 //     "\x1b[38;5;72m35\x1b[38;5;239m5",
  88 //     "\x1b[38;5;72m36\x1b[38;5;239m6",
  89 //     "\x1b[38;5;72m37\x1b[38;5;239m7",
  90 //     "\x1b[38;5;72m38\x1b[38;5;239m8",
  91 //     "\x1b[38;5;72m39\x1b[38;5;239m9",
  92 //     "\x1b[38;5;72m3a\x1b[38;5;239m:",
  93 //     "\x1b[38;5;72m3b\x1b[38;5;239m;",
  94 //     "\x1b[38;5;72m3c\x1b[38;5;239m<",
  95 //     "\x1b[38;5;72m3d\x1b[38;5;239m=",
  96 //     "\x1b[38;5;72m3e\x1b[38;5;239m>",
  97 //     "\x1b[38;5;72m3f\x1b[38;5;239m?",
  98 //     "\x1b[38;5;72m40\x1b[38;5;239m@",
  99 //     "\x1b[38;5;72m41\x1b[38;5;239mA",
 100 //     "\x1b[38;5;72m42\x1b[38;5;239mB",
 101 //     "\x1b[38;5;72m43\x1b[38;5;239mC",
 102 //     "\x1b[38;5;72m44\x1b[38;5;239mD",
 103 //     "\x1b[38;5;72m45\x1b[38;5;239mE",
 104 //     "\x1b[38;5;72m46\x1b[38;5;239mF",
 105 //     "\x1b[38;5;72m47\x1b[38;5;239mG",
 106 //     "\x1b[38;5;72m48\x1b[38;5;239mH",
 107 //     "\x1b[38;5;72m49\x1b[38;5;239mI",
 108 //     "\x1b[38;5;72m4a\x1b[38;5;239mJ",
 109 //     "\x1b[38;5;72m4b\x1b[38;5;239mK",
 110 //     "\x1b[38;5;72m4c\x1b[38;5;239mL",
 111 //     "\x1b[38;5;72m4d\x1b[38;5;239mM",
 112 //     "\x1b[38;5;72m4e\x1b[38;5;239mN",
 113 //     "\x1b[38;5;72m4f\x1b[38;5;239mO",
 114 //     "\x1b[38;5;72m50\x1b[38;5;239mP",
 115 //     "\x1b[38;5;72m51\x1b[38;5;239mQ",
 116 //     "\x1b[38;5;72m52\x1b[38;5;239mR",
 117 //     "\x1b[38;5;72m53\x1b[38;5;239mS",
 118 //     "\x1b[38;5;72m54\x1b[38;5;239mT",
 119 //     "\x1b[38;5;72m55\x1b[38;5;239mU",
 120 //     "\x1b[38;5;72m56\x1b[38;5;239mV",
 121 //     "\x1b[38;5;72m57\x1b[38;5;239mW",
 122 //     "\x1b[38;5;72m58\x1b[38;5;239mX",
 123 //     "\x1b[38;5;72m59\x1b[38;5;239mY",
 124 //     "\x1b[38;5;72m5a\x1b[38;5;239mZ",
 125 //     "\x1b[38;5;72m5b\x1b[38;5;239m[",
 126 //     "\x1b[38;5;72m5c\x1b[38;5;239m\\",
 127 //     "\x1b[38;5;72m5d\x1b[38;5;239m]",
 128 //     "\x1b[38;5;72m5e\x1b[38;5;239m^",
 129 //     "\x1b[38;5;72m5f\x1b[38;5;239m_",
 130 //     "\x1b[38;5;72m60\x1b[38;5;239m`",
 131 //     "\x1b[38;5;72m61\x1b[38;5;239ma",
 132 //     "\x1b[38;5;72m62\x1b[38;5;239mb",
 133 //     "\x1b[38;5;72m63\x1b[38;5;239mc",
 134 //     "\x1b[38;5;72m64\x1b[38;5;239md",
 135 //     "\x1b[38;5;72m65\x1b[38;5;239me",
 136 //     "\x1b[38;5;72m66\x1b[38;5;239mf",
 137 //     "\x1b[38;5;72m67\x1b[38;5;239mg",
 138 //     "\x1b[38;5;72m68\x1b[38;5;239mh",
 139 //     "\x1b[38;5;72m69\x1b[38;5;239mi",
 140 //     "\x1b[38;5;72m6a\x1b[38;5;239mj",
 141 //     "\x1b[38;5;72m6b\x1b[38;5;239mk",
 142 //     "\x1b[38;5;72m6c\x1b[38;5;239ml",
 143 //     "\x1b[38;5;72m6d\x1b[38;5;239mm",
 144 //     "\x1b[38;5;72m6e\x1b[38;5;239mn",
 145 //     "\x1b[38;5;72m6f\x1b[38;5;239mo",
 146 //     "\x1b[38;5;72m70\x1b[38;5;239mp",
 147 //     "\x1b[38;5;72m71\x1b[38;5;239mq",
 148 //     "\x1b[38;5;72m72\x1b[38;5;239mr",
 149 //     "\x1b[38;5;72m73\x1b[38;5;239ms",
 150 //     "\x1b[38;5;72m74\x1b[38;5;239mt",
 151 //     "\x1b[38;5;72m75\x1b[38;5;239mu",
 152 //     "\x1b[38;5;72m76\x1b[38;5;239mv",
 153 //     "\x1b[38;5;72m77\x1b[38;5;239mw",
 154 //     "\x1b[38;5;72m78\x1b[38;5;239mx",
 155 //     "\x1b[38;5;72m79\x1b[38;5;239my",
 156 //     "\x1b[38;5;72m7a\x1b[38;5;239mz",
 157 //     "\x1b[38;5;72m7b\x1b[38;5;239m{",
 158 //     "\x1b[38;5;72m7c\x1b[38;5;239m|",
 159 //     "\x1b[38;5;72m7d\x1b[38;5;239m}",
 160 //     "\x1b[38;5;72m7e\x1b[38;5;239m~",
 161 //     "\x1b[38;5;246m7f ",
 162 //     "\x1b[38;5;246m80 ",
 163 //     "\x1b[38;5;246m81 ",
 164 //     "\x1b[38;5;246m82 ",
 165 //     "\x1b[38;5;246m83 ",
 166 //     "\x1b[38;5;246m84 ",
 167 //     "\x1b[38;5;246m85 ",
 168 //     "\x1b[38;5;246m86 ",
 169 //     "\x1b[38;5;246m87 ",
 170 //     "\x1b[38;5;246m88 ",
 171 //     "\x1b[38;5;246m89 ",
 172 //     "\x1b[38;5;246m8a ",
 173 //     "\x1b[38;5;246m8b ",
 174 //     "\x1b[38;5;246m8c ",
 175 //     "\x1b[38;5;246m8d ",
 176 //     "\x1b[38;5;246m8e ",
 177 //     "\x1b[38;5;246m8f ",
 178 //     "\x1b[38;5;246m90 ",
 179 //     "\x1b[38;5;246m91 ",
 180 //     "\x1b[38;5;246m92 ",
 181 //     "\x1b[38;5;246m93 ",
 182 //     "\x1b[38;5;246m94 ",
 183 //     "\x1b[38;5;246m95 ",
 184 //     "\x1b[38;5;246m96 ",
 185 //     "\x1b[38;5;246m97 ",
 186 //     "\x1b[38;5;246m98 ",
 187 //     "\x1b[38;5;246m99 ",
 188 //     "\x1b[38;5;246m9a ",
 189 //     "\x1b[38;5;246m9b ",
 190 //     "\x1b[38;5;246m9c ",
 191 //     "\x1b[38;5;246m9d ",
 192 //     "\x1b[38;5;246m9e ",
 193 //     "\x1b[38;5;246m9f ",
 194 //     "\x1b[38;5;246ma0 ",
 195 //     "\x1b[38;5;246ma1 ",
 196 //     "\x1b[38;5;246ma2 ",
 197 //     "\x1b[38;5;246ma3 ",
 198 //     "\x1b[38;5;246ma4 ",
 199 //     "\x1b[38;5;246ma5 ",
 200 //     "\x1b[38;5;246ma6 ",
 201 //     "\x1b[38;5;246ma7 ",
 202 //     "\x1b[38;5;246ma8 ",
 203 //     "\x1b[38;5;246ma9 ",
 204 //     "\x1b[38;5;246maa ",
 205 //     "\x1b[38;5;246mab ",
 206 //     "\x1b[38;5;246mac ",
 207 //     "\x1b[38;5;246mad ",
 208 //     "\x1b[38;5;246mae ",
 209 //     "\x1b[38;5;246maf ",
 210 //     "\x1b[38;5;246mb0 ",
 211 //     "\x1b[38;5;246mb1 ",
 212 //     "\x1b[38;5;246mb2 ",
 213 //     "\x1b[38;5;246mb3 ",
 214 //     "\x1b[38;5;246mb4 ",
 215 //     "\x1b[38;5;246mb5 ",
 216 //     "\x1b[38;5;246mb6 ",
 217 //     "\x1b[38;5;246mb7 ",
 218 //     "\x1b[38;5;246mb8 ",
 219 //     "\x1b[38;5;246mb9 ",
 220 //     "\x1b[38;5;246mba ",
 221 //     "\x1b[38;5;246mbb ",
 222 //     "\x1b[38;5;246mbc ",
 223 //     "\x1b[38;5;246mbd ",
 224 //     "\x1b[38;5;246mbe ",
 225 //     "\x1b[38;5;246mbf ",
 226 //     "\x1b[38;5;246mc0 ",
 227 //     "\x1b[38;5;246mc1 ",
 228 //     "\x1b[38;5;246mc2 ",
 229 //     "\x1b[38;5;246mc3 ",
 230 //     "\x1b[38;5;246mc4 ",
 231 //     "\x1b[38;5;246mc5 ",
 232 //     "\x1b[38;5;246mc6 ",
 233 //     "\x1b[38;5;246mc7 ",
 234 //     "\x1b[38;5;246mc8 ",
 235 //     "\x1b[38;5;246mc9 ",
 236 //     "\x1b[38;5;246mca ",
 237 //     "\x1b[38;5;246mcb ",
 238 //     "\x1b[38;5;246mcc ",
 239 //     "\x1b[38;5;246mcd ",
 240 //     "\x1b[38;5;246mce ",
 241 //     "\x1b[38;5;246mcf ",
 242 //     "\x1b[38;5;246md0 ",
 243 //     "\x1b[38;5;246md1 ",
 244 //     "\x1b[38;5;246md2 ",
 245 //     "\x1b[38;5;246md3 ",
 246 //     "\x1b[38;5;246md4 ",
 247 //     "\x1b[38;5;246md5 ",
 248 //     "\x1b[38;5;246md6 ",
 249 //     "\x1b[38;5;246md7 ",
 250 //     "\x1b[38;5;246md8 ",
 251 //     "\x1b[38;5;246md9 ",
 252 //     "\x1b[38;5;246mda ",
 253 //     "\x1b[38;5;246mdb ",
 254 //     "\x1b[38;5;246mdc ",
 255 //     "\x1b[38;5;246mdd ",
 256 //     "\x1b[38;5;246mde ",
 257 //     "\x1b[38;5;246mdf ",
 258 //     "\x1b[38;5;246me0 ",
 259 //     "\x1b[38;5;246me1 ",
 260 //     "\x1b[38;5;246me2 ",
 261 //     "\x1b[38;5;246me3 ",
 262 //     "\x1b[38;5;246me4 ",
 263 //     "\x1b[38;5;246me5 ",
 264 //     "\x1b[38;5;246me6 ",
 265 //     "\x1b[38;5;246me7 ",
 266 //     "\x1b[38;5;246me8 ",
 267 //     "\x1b[38;5;246me9 ",
 268 //     "\x1b[38;5;246mea ",
 269 //     "\x1b[38;5;246meb ",
 270 //     "\x1b[38;5;246mec ",
 271 //     "\x1b[38;5;246med ",
 272 //     "\x1b[38;5;246mee ",
 273 //     "\x1b[38;5;246mef ",
 274 //     "\x1b[38;5;246mf0 ",
 275 //     "\x1b[38;5;246mf1 ",
 276 //     "\x1b[38;5;246mf2 ",
 277 //     "\x1b[38;5;246mf3 ",
 278 //     "\x1b[38;5;246mf4 ",
 279 //     "\x1b[38;5;246mf5 ",
 280 //     "\x1b[38;5;246mf6 ",
 281 //     "\x1b[38;5;246mf7 ",
 282 //     "\x1b[38;5;246mf8 ",
 283 //     "\x1b[38;5;246mf9 ",
 284 //     "\x1b[38;5;246mfa ",
 285 //     "\x1b[38;5;246mfb ",
 286 //     "\x1b[38;5;246mfc ",
 287 //     "\x1b[38;5;246mfd ",
 288 //     "\x1b[38;5;246mfe ",
 289 //     "\x1b[38;5;209mff ",
 290 // };

     File: chex/hex.rc
   1 // https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
   2 // windres -O coff -o hex.res hex.rc
   3 
   4 IDI_ICON1 ICON "logo.ico"

     File: chex/hex-styles.awk
   1 #!/usr/bin/awk -f
   2 
   3 # bytes with all bits off
   4 $0 == 0 {
   5     print "\"\\x1b[38;5;111m00 \","
   6     next
   7 }
   8 
   9 # ascii symbol which need backslashing
  10 $0 == 34 || $0 == 92 {
  11     printf "\"\\x1b[38;5;72m%02x\\x1b[38;5;239m\\%c\",\n", $0 + 0, $0
  12     next
  13 }
  14 
  15 # all other ascii symbol
  16 32 <= $0 && $0 <= 126 {
  17     printf "\"\\x1b[38;5;72m%02x\\x1b[38;5;239m%c\",\n", $0 + 0, $0
  18     next
  19 }
  20 
  21 # bytes with all bits on
  22 $0 == 255 {
  23     print "\"\\x1b[38;5;209mff \","
  24     next
  25 }
  26 
  27 # all other bytes
  28 1 {
  29     printf "\"\\x1b[38;5;246m%02x \",\n", $0 + 0
  30     next
  31 }

     File: chex/info.txt
   1 chex [options...] [filenames...]
   2 
   3 Colored HEXadecimal is a simple hexadecimal (base-16) viewer to inspect
   4 bytes from files or standard input.
   5 
   6 Each line shows the starting offset for the bytes shown, 16 of the bytes
   7 themselves in base-16 notation, and any ASCII codes when the byte values
   8 are in the typical ASCII range.
   9 
  10 The base-16 codes are color-coded, with most bytes shown in gray, while
  11 all-1 and all-0 bytes are shown in orange and blue respectively.
  12 
  13 All-0 bytes are the commonest kind in most binary file types and, along
  14 with all-1 bytes are also a special case worth noticing when exploring
  15 binary data, so it makes sense for them to stand out right away.
  16 
  17 
  18 Options
  19 
  20     -h, --h            show this help message
  21     -help, --help      aliases for option -h
  22 
  23     -p, --p            plain-text output, without ANSI styles
  24     -plain, --plain    aliases for option -p

     File: chex/logo.ico   <BINARY>

     File: chex/logo.png   <BINARY>

     File: chex/main.c
   1 #include <fcntl.h>
   2 #include <math.h>
   3 #include <stdbool.h>
   4 #include <stdio.h>
   5 #include <stdlib.h>
   6 #include <string.h>
   7 #include <sys/stat.h>
   8 
   9 // building with CHEX_COMPACT_OUTPUT defined makes `chex` output many fewer
  10 // bytes, at the cost of using arguably worse colors
  11 
  12 #ifdef CHEX_COMPACT_OUTPUT
  13 #define OUTPUT_FOR_00 "\x1b[34m00 "
  14 #define OUTPUT_FOR_FF "\x1b[33mff "
  15 #define NORMAL_HEX_STYLE "\x1b[37m"
  16 #define ASCII_HEX_STYLE "\x1b[32m"
  17 #define ASCII_BYTE_STYLE "\x1b[30m"
  18 #else
  19 #define OUTPUT_FOR_00 "\x1b[38;5;111m00 "
  20 #define OUTPUT_FOR_FF "\x1b[38;5;209mff "
  21 #define NORMAL_HEX_STYLE "\x1b[38;5;246m"
  22 #define ASCII_HEX_STYLE "\x1b[38;5;72m"
  23 #define ASCII_BYTE_STYLE "\x1b[38;5;239m"
  24 #endif
  25 
  26 extern const char* info;
  27 // extern const char* styled_hex_results[256];
  28 
  29 // styled_hex_lengths is a lookup table for the string lengths of values in
  30 // styled_hex_results
  31 // size_t styled_hex_lengths[256] = {};
  32 
  33 // bufwriter is, as the name implies, a buffered-writer: when it's aimed at
  34 // stdout, it considerably speeds up this app, as intended
  35 typedef struct bufwriter {
  36     // buf is the buffer proper
  37     unsigned char* buf;
  38 
  39     // len is how many bytes of the buffer are currently being used
  40     size_t len;
  41 
  42     // cap is the capacity of the buffer, or the most bytes it can hold
  43     size_t cap;
  44 
  45     // out is the destination of all that's written into the buffer
  46     FILE* out;
  47 
  48     // done signals when/if no more output is accepted at the destination
  49     bool done;
  50 } bufwriter;
  51 
  52 // new_bufwriter is the constructor for type bufwriter
  53 bufwriter new_bufwriter(FILE* dst, size_t cap) {
  54     bufwriter res;
  55     res.cap = cap;
  56     res.done = false;
  57     res.len = 0;
  58     res.out = dst;
  59     res.buf = malloc(res.cap);
  60     return res;
  61 }
  62 
  63 // flush does as it says: it empties the buffer after ensuring its bytes end
  64 // on their intended destination
  65 void flush(bufwriter* w) {
  66     if (w->len > 0 && fwrite(w->buf, w->len, 1, w->out) < 1) {
  67         w->done = true;
  68     }
  69     w->len = 0;
  70 }
  71 
  72 // close_bufwriter ensures all output is shown and deallocates the buffer
  73 void close_bufwriter(bufwriter* w) {
  74     flush(w);
  75     free(w->buf);
  76     w->buf = NULL;
  77 }
  78 
  79 // write_bytes does as it says, minimizing the number of calls to fwrite
  80 void write_bytes(bufwriter* w, const unsigned char* src, size_t len) {
  81     if (w->len + len < w->cap) {
  82         // all bytes fit into buffer
  83         memcpy(w->buf + w->len, src, len);
  84         w->len += len;
  85         return;
  86     }
  87 
  88     // ensure current buffer bytes go out, before crossing strides
  89     flush(w);
  90 
  91     // emit all chunks striding beyond/at the buffer's capacity
  92     for (; len >= w->cap; src += w->cap, len -= w->cap) {
  93         if (fwrite(src, w->cap, 1, w->out) < 1) {
  94             w->done = true;
  95             return;
  96         }
  97     }
  98 
  99     // now all, if any, remaining bytes will fit into the buffer
 100     memcpy(w->buf, src, len);
 101     w->len += len;
 102 }
 103 
 104 // write_byte does as it says
 105 void write_byte(bufwriter* w, unsigned char b) {
 106     if (w->len >= w->cap) {
 107         flush(w);
 108     }
 109 
 110     unsigned char* ptr = w->buf + w->len;
 111     *ptr = b;
 112     w->len++;
 113 }
 114 
 115 // EMIT_CONST abstracts a common use-case of the bufwriter, which is
 116 // emitting string constants without their final null byte
 117 #define EMIT_CONST(w, x) write_bytes(w, (unsigned char*)x, sizeof(x) - 1)
 118 
 119 // write_hex is faster than calling fprintf(w, "%02x", b): this matters
 120 // because it's called for every input byte
 121 void write_hex(bufwriter* w, unsigned char b) {
 122     const char* hex_digits = "0123456789abcdef";
 123     write_byte(w, hex_digits[b >> 4]);
 124     write_byte(w, hex_digits[b & 0x0f]);
 125 }
 126 
 127 // write_styled_hex emits an ANSI color-coded hexadecimal representation
 128 // of the byte given
 129 void write_styled_hex(bufwriter* w, unsigned char b) {
 130     // all-bits-off is almost always noteworthy
 131     if (b == 0) {
 132         EMIT_CONST(w, OUTPUT_FOR_00);
 133         return;
 134     }
 135     // all-bits-on is often noteworthy
 136     if (b == 0xff) {
 137         EMIT_CONST(w, OUTPUT_FOR_FF);
 138         return;
 139     }
 140 
 141     // regular ASCII display symbols
 142     if (32 <= b && b <= 126) {
 143         EMIT_CONST(w, ASCII_HEX_STYLE);
 144         write_hex(w, b);
 145         EMIT_CONST(w, ASCII_BYTE_STYLE);
 146         write_byte(w, b);
 147         return;
 148     }
 149 
 150     // ASCII control values, and other bytes beyond displayable ASCII
 151     EMIT_CONST(w, NORMAL_HEX_STYLE);
 152     write_hex(w, b);
 153     write_byte(w, ' ');
 154 }
 155 
 156 // ruler emits a ruler-like string of spaced-out symbols
 157 void ruler(bufwriter* w, size_t bytes_per_line) {
 158     const size_t gap = 4;
 159     if (bytes_per_line < gap) {
 160         return;
 161     }
 162 
 163     EMIT_CONST(w, "             ·");
 164     for (size_t n = bytes_per_line - gap; n >= gap; n -= gap) {
 165         EMIT_CONST(w, "           ·");
 166     }
 167 }
 168 
 169 // write_commas_uint shows a number by separating 3-digits groups with commas
 170 void write_commas_uint(bufwriter* w, size_t n) {
 171     if (n == 0) {
 172         EMIT_CONST(w, "0");
 173         return;
 174     }
 175 
 176     size_t digits;
 177     // 20 is the most digits unsigned 64-bit ints can ever need
 178     unsigned char buf[24];
 179     for (digits = 0; n > 0; digits++, n /= 10) {
 180         buf[sizeof(buf) - 1 - digits] = (n % 10) + '0';
 181     }
 182 
 183     // now emit the leading digits, which may not come in 3
 184     size_t leading = digits % 3;
 185     if (leading == 0) {
 186         // avoid having a comma before the first digit
 187         leading = digits < 3 ? digits : 3;
 188     }
 189     unsigned char* start = buf + sizeof(buf) - digits;
 190     write_bytes(w, start, leading);
 191     start += leading;
 192     digits -= leading;
 193 
 194     // now emit all remaining digits in groups of 3, alternating styles
 195     for (; digits > 0; start += 3, digits -= 3) {
 196         write_byte(w, ',');
 197         write_bytes(w, start, 3);
 198     }
 199 }
 200 
 201 // output_state ties all values representing the current state shared across
 202 // all functions involved in interpreting the input-buffer and showing its
 203 // bytes and ASCII values
 204 typedef struct output_state {
 205     // the whole input-buffer and its currently-used length in bytes
 206     unsigned char* buf;
 207     size_t buflen;
 208 
 209     // the ASCII-text buffer and its currently-used length in bytes
 210     unsigned char* txt;
 211     size_t txtlen;
 212 
 213     // offset is the byte counter, shown at the start of each line
 214     size_t offset;
 215 
 216     // linewidth is how many bytes each line can show at most
 217     size_t linewidth;
 218 
 219     // lines is the line counter, which is used to provide periodic
 220     // breather lines, to make eye-scanning big output blobs easier
 221     size_t lines;
 222 
 223     // showtxt is a hint on whether it's sensible to show the ASCII-text
 224     // buffer for the current line
 225     bool showtxt;
 226 } output_state;
 227 
 228 // peek_ascii looks 2 lines ahead in the buffer to get all ASCII-like runs
 229 // of bytes, which are later meant to show on the side panel
 230 void peek_ascii(size_t i, size_t end, output_state* os) {
 231     unsigned char prev = 0;
 232     os->txtlen = 0;
 233 
 234     for (size_t j = i; j < end; j++) {
 235         const unsigned char b = os->buf[j];
 236 
 237         if (' ' < b && b <= '~') {
 238             bool first = os->txtlen == 0;
 239             if (first) {
 240                 // show ASCII panel, if the symbols start on the current line
 241                 os->showtxt = j - i < os->linewidth;
 242             }
 243 
 244             // add a space before the symbol, when it's the start of a `word`
 245             if ((prev <= ' ' || prev > '~') && !first) {
 246                 os->txt[os->txtlen] = ' ';
 247                 os->txtlen++;
 248             }
 249 
 250             // add the symbol itself
 251             os->txt[os->txtlen] = b;
 252             os->txtlen++;
 253         }
 254 
 255         prev = b;
 256     }
 257 }
 258 
 259 // write_plain_uint is the unstyled counterpart of func write_styled_uint
 260 void write_plain_uint(bufwriter* w, size_t n) {
 261     if (n < 1) {
 262         EMIT_CONST(w, "       0");
 263         return;
 264     }
 265 
 266     size_t digits;
 267     // 20 is the most digits unsigned 64-bit ints can ever need
 268     unsigned char buf[24];
 269     for (digits = 0; n > 0; digits++, n /= 10) {
 270         buf[sizeof(buf) - 1 - digits] = (n % 10) + '0';
 271     }
 272 
 273     // left-pad the coming digits up to 8 chars
 274     if (digits < 8) {
 275         write_bytes(w, (unsigned char*)"        ", 8 - digits);
 276     }
 277 
 278     // emit all digits
 279     unsigned char* start = buf + sizeof(buf) - digits;
 280     write_bytes(w, start, digits);
 281 }
 282 
 283 // write_styled_uint is a quick way to emit the offset-counter showing at the
 284 // start of each line; it assumes 8-item left-padding of values, unless the
 285 // numbers are too big for that
 286 void write_styled_uint(bufwriter* w, size_t n) {
 287     if (n < 1) {
 288         EMIT_CONST(w, "       0");
 289         return;
 290     }
 291 
 292     size_t digits;
 293     // 20 is the most digits unsigned 64-bit ints can ever need
 294     unsigned char buf[24];
 295     for (digits = 0; n > 0; digits++, n /= 10) {
 296         buf[sizeof(buf) - 1 - digits] = (n % 10) + '0';
 297     }
 298 
 299     // left-pad the coming digits up to 8 chars
 300     if (digits < 8) {
 301         write_bytes(w, (unsigned char*)"        ", 8 - digits);
 302     }
 303 
 304     // now emit the leading digits, which may be fewer than 3
 305     size_t leading = digits % 3;
 306     unsigned char* start = buf + sizeof(buf) - digits;
 307     write_bytes(w, start, leading);
 308     start += leading;
 309     digits -= leading;
 310 
 311     // now emit all remaining digits in groups of 3, alternating styles
 312     bool styled = leading != 0;
 313     for (; digits > 0; start += 3, digits -= 3, styled = !styled) {
 314         if (styled) {
 315             EMIT_CONST(w, "\x1b[38;5;243m");
 316             write_bytes(w, start, 3);
 317             EMIT_CONST(w, "\x1b[0m");
 318         } else {
 319             write_bytes(w, start, 3);
 320         }
 321     }
 322 }
 323 
 324 // emit_styled_file_info emits an ANSI-styled line showing a filename and the
 325 // file's size in bytes
 326 void emit_styled_file_info(bufwriter* w, const char* path, size_t nbytes) {
 327     EMIT_CONST(w, "");
 328     write_bytes(w, (unsigned char*)path, strlen(path));
 329     EMIT_CONST(w, "  \x1b[38;5;245m(");
 330     write_commas_uint(w, nbytes);
 331     EMIT_CONST(w, " bytes)\x1b[0m\n");
 332 }
 333 
 334 // emit_plain_file_info is the unstyled counterpart of func emit_styled_file_info
 335 void emit_plain_file_info(bufwriter* w, const char* path, size_t nbytes) {
 336     EMIT_CONST(w, "");
 337     write_bytes(w, (unsigned char*)path, strlen(path));
 338     EMIT_CONST(w, "  (");
 339     write_commas_uint(w, nbytes);
 340     EMIT_CONST(w, " bytes)\n");
 341 }
 342 
 343 // emit_styled_line handles the details of showing a styled line out of the current
 344 // input-buffer chunk
 345 void emit_styled_line(bufwriter* w, size_t i, size_t end, output_state* os) {
 346     for (size_t j = i; j < end; j++, os->offset++) {
 347         const unsigned char b = os->buf[j];
 348 
 349         if (j % os->linewidth == 0) {
 350             // show a ruler every few lines to make eye-scanning easier
 351             if (os->lines % 5 == 0 && os->lines > 0) {
 352                 EMIT_CONST(w, "        \x1b[38;5;245m");
 353                 ruler(w, os->linewidth);
 354                 EMIT_CONST(w, "\x1b[0m\n");
 355             }
 356             os->lines++;
 357 
 358             // start next line with offset of its 1st item, also
 359             // changing the background color for the colored hex
 360             // code which will follow
 361             // fprintf(stdout, "%8d", os->offset);
 362             write_styled_uint(w, os->offset);
 363             EMIT_CONST(w, "  \x1b[48;5;254m");
 364         }
 365 
 366         // show the current byte `with style`
 367         write_styled_hex(w, b);
 368         // const unsigned char* s = (const unsigned char*)styled_hex_results[b];
 369         // write_bytes(w, s, styled_hex_lengths[b]);
 370     }
 371 
 372     if (os->showtxt) {
 373         EMIT_CONST(w, "\x1b[0m  ");
 374         for (size_t j = end - i; j < os->linewidth; j++) {
 375             EMIT_CONST(w, "   ");
 376         }
 377 
 378         write_bytes(w, os->txt, os->txtlen);
 379         write_byte(w, '\n');
 380         return;
 381     }
 382     EMIT_CONST(w, "\x1b[0m\n");
 383 }
 384 
 385 // emit_plain_line handles the details of showing a plain (unstyled) line out
 386 // of the current input-buffer chunk
 387 void emit_plain_line(bufwriter* w, size_t i, size_t end, output_state* os) {
 388     for (size_t j = i; j < end; j++, os->offset++) {
 389         const unsigned char b = os->buf[j];
 390 
 391         if (j % os->linewidth == 0) {
 392             // show a ruler every few lines to make eye-scanning easier
 393             if (os->lines % 5 == 0 && os->lines > 0) {
 394                 // EMIT_CONST(w, "        ");
 395                 // ruler(w, os->linewidth);
 396                 write_byte(w, '\n');
 397             }
 398             os->lines++;
 399 
 400             // start next line with offset of its 1st item, also
 401             // changing the background color for the colored hex
 402             // code which will follow
 403             // fprintf(stdout, "%8d", os->offset);
 404             write_plain_uint(w, os->offset);
 405             EMIT_CONST(w, "  ");
 406         }
 407 
 408         // show the current byte `with style`
 409         write_hex(w, b);
 410         write_byte(w, ' ');
 411     }
 412 
 413     if (os->showtxt) {
 414         EMIT_CONST(w, "  ");
 415         for (size_t j = end - i; j < os->linewidth; j++) {
 416             EMIT_CONST(w, "   ");
 417         }
 418 
 419         write_bytes(w, os->txt, os->txtlen);
 420         write_byte(w, '\n');
 421         return;
 422     }
 423     write_byte(w, '\n');
 424 }
 425 
 426 // config has all the settings used to emit output
 427 typedef struct config {
 428     // bytes_per_line determines the `width` of output lines
 429     size_t bytes_per_line;
 430 
 431     // emit_file_info is chosen to emit file-info with colors or plainly
 432     void (*emit_file_info)(bufwriter* w, const char* path, size_t nbytes);
 433 
 434     // emit_line is chosen to emit hex bytes with colors or plainly
 435     void (*emit_line)(bufwriter* w, size_t i, size_t end, output_state* os);
 436 } config;
 437 
 438 // handle_reader shows all bytes read from the source given as colored hex
 439 // values, showing offsets and ASCII symbols on the sides of each output line
 440 void handle_reader(bufwriter* w, FILE* src, config cfg) {
 441     const size_t bufcap = 32 * 1024;
 442     // limit line-width to the buffer's capacity
 443     if (cfg.bytes_per_line > bufcap) {
 444         cfg.bytes_per_line = bufcap;
 445     }
 446 
 447     const size_t two_lines = 2 * cfg.bytes_per_line;
 448     unsigned char txt[two_lines];
 449 
 450     unsigned char buf[bufcap];
 451     // ensure the effective buffer-size is a multiple of the line-width
 452     size_t max = bufcap - bufcap % cfg.bytes_per_line;
 453 
 454     output_state os;
 455     os.buf = buf;
 456     os.linewidth = cfg.bytes_per_line;
 457     os.lines = 0;
 458     os.offset = 0;
 459     os.txt = txt;
 460 
 461     const size_t one_line = cfg.bytes_per_line;
 462 
 463     while (!w->done) {
 464         os.buflen = fread(&buf, sizeof(unsigned char), max, src);
 465         if (os.buflen < 1) {
 466             // assume input is over when no bytes were read
 467             return;
 468         }
 469 
 470         for (size_t i = 0; i < os.buflen; i += one_line) {
 471             size_t end;
 472 
 473             // remember all ASCII symbols in current pair of output lines
 474             end = i + two_lines < os.buflen ? i + two_lines : os.buflen;
 475             peek_ascii(i, end, &os);
 476 
 477             // show current output line
 478             end = i + one_line < os.buflen ? i + one_line : os.buflen;
 479             cfg.emit_line(w, i, end, &os);
 480         }
 481     }
 482 }
 483 
 484 // handle_file handles data from the filename given; returns false only when
 485 // the file can't be opened
 486 bool handle_file(bufwriter* w, const char* path, config cfg) {
 487     // a `-` filename stands for the standard input
 488     if (strcmp(path, "-") == 0) {
 489         EMIT_CONST(w, "• <stdin>\n");
 490         EMIT_CONST(w, "\n");
 491         handle_reader(w, stdin, cfg);
 492         return true;
 493     }
 494 
 495     FILE* f = fopen(path, "rb");
 496     if (f == NULL) {
 497         // ensure currently-buffered/deferred output shows up right now: not
 498         // doing so may scramble results in the common case where stdout and
 499         // stderr are the same, thus confusing users
 500         flush(w);
 501 
 502         fprintf(stderr, "\x1b[31mcan't open file named %s\x1b[0m\n", path);
 503         return false;
 504     }
 505 
 506     // get the file size
 507     struct stat st;
 508     fstat(fileno(f), &st);
 509 
 510     // show output
 511     cfg.emit_file_info(w, path, st.st_size);
 512     EMIT_CONST(w, "\n");
 513     handle_reader(w, f, cfg);
 514 
 515     fclose(f);
 516     return true;
 517 }
 518 
 519 // is_help_option simplifies control-flow for func run
 520 bool is_help_option(char* s) {
 521     return false ||
 522         strcmp(s, "-h") == 0 ||
 523         strcmp(s, "-help") == 0 ||
 524         strcmp(s, "--h") == 0 ||
 525         strcmp(s, "--help") == 0;
 526 }
 527 
 528 // is_plain_option simplifies control-flow for func run
 529 bool is_plain_option(char* s) {
 530     return false ||
 531         strcmp(s, "-p") == 0 ||
 532         strcmp(s, "-plain") == 0 ||
 533         strcmp(s, "--p") == 0 ||
 534         strcmp(s, "--plain") == 0;
 535 }
 536 
 537 // run returns the number of errors
 538 size_t run(int argc, char** argv) {
 539     config cfg;
 540     cfg.bytes_per_line = 16;
 541     cfg.emit_line = &emit_styled_line;
 542     cfg.emit_file_info = &emit_styled_file_info;
 543 
 544     // handle special cmd-line options and count filenames
 545     size_t fnames = 0;
 546     for (size_t i = 1; i < argc; i++) {
 547         if (is_help_option(argv[i])) {
 548             // help option is handled right away, also quitting the app
 549             fprintf(stderr, "%s", info);
 550             return 0;
 551         }
 552         if (is_plain_option(argv[i])) {
 553             cfg.emit_line = &emit_plain_line;
 554             cfg.emit_file_info = &emit_plain_file_info;
 555             continue;
 556         }
 557         fnames++;
 558     }
 559 
 560     bufwriter w = new_bufwriter(stdout, 32 * 1024);
 561 
 562     // no filenames means use stdin as the only input
 563     if (fnames == 0) {
 564         EMIT_CONST(&w, "• <stdin>\n");
 565         EMIT_CONST(&w, "\n");
 566         handle_reader(&w, stdin, cfg);
 567         close_bufwriter(&w);
 568         return 0;
 569     }
 570 
 571     size_t errors = 0;
 572     bool first_file = true;
 573 
 574     // handle all filenames given
 575     for (size_t i = 1; i < argc && !w.done; i++) {
 576         if (i == 1 && is_plain_option(argv[i])) {
 577             // special cmd-line options aren't filenames
 578             continue;
 579         }
 580 
 581         if (!first_file) {
 582             // put an empty line between adjacent hex outputs
 583             write_byte(&w, '\n');
 584         }
 585 
 586         if (!handle_file(&w, argv[i], cfg)) {
 587             errors++;
 588         }
 589         first_file = false;
 590     }
 591 
 592     close_bufwriter(&w);
 593     return errors;
 594 }
 595 
 596 int main(int argc, char** argv) {
 597 #ifdef _WIN32
 598     setmode(fileno(stdin), O_BINARY);
 599     // ensure output lines end in LF instead of CRLF on windows
 600     setmode(fileno(stdout), O_BINARY);
 601     setmode(fileno(stderr), O_BINARY);
 602 #endif
 603 
 604     // disable automatic stdio buffering, in favor of explicit buffering
 605     setvbuf(stdin, NULL, _IONBF, 0);
 606     setvbuf(stdout, NULL, _IONBF, 0);
 607     setvbuf(stderr, NULL, _IONBF, 0);
 608 
 609     // for (size_t i = 0; i < 256; i++) {
 610     //     styled_hex_lengths[i] = strlen(styled_hex_results[i]);
 611     // }
 612     return run(argc, argv) == 0 ? 0 : 1;
 613 }

     File: chex/mit-license.txt
   1 The MIT License (MIT)
   2 
   3 Copyright © 2024 pacman64
   4 
   5 Permission is hereby granted, free of charge, to any person obtaining a copy of
   6 this software and associated documentation files (the “Software”), to deal
   7 in the Software without restriction, including without limitation the rights to
   8 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
   9 of the Software, and to permit persons to whom the Software is furnished to do
  10 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.