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.