├── LICENSE ├── README.md ├── build.sh └── cembed.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 rxi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # cembed 3 | A small utility for embedding files in a C header 4 | 5 | ``` 6 | $ cat test.txt 7 | hello world 8 | 9 | $ cat test2.txt 10 | goodbye world 11 | 12 | $ cembed -t my_files test.txt test2.txt 13 | static unsigned char test_txt[] = { 14 | 104,101,108,108,111,32,119,111,114,108,100,10 15 | }; 16 | 17 | static unsigned char test2_txt[] = { 18 | 103,111,111,100,98,121,101,32,119,111,114,108,100,10 19 | }; 20 | 21 | static struct { char *filename; unsigned char *data; int size; } my_files[] = { 22 | { "test.txt", test_txt, (int) sizeof(test_txt) }, 23 | { "test2.txt", test2_txt, (int) sizeof(test2_txt) }, 24 | { 0 } 25 | }; 26 | ``` 27 | 28 | ## License 29 | This project is free software; you can redistribute it and/or modify it under 30 | the terms of the MIT license. See LICENSE for details. 31 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -n "$1" ] && [ $1 = debug ]; then 4 | gcc -g -fsanitize=address -Wall -Wextra -std=c99 -o cembed cembed.c 5 | else 6 | gcc -O3 -Wall -Wextra -std=c99 -o cembed cembed.c 7 | strip cembed 8 | fi 9 | -------------------------------------------------------------------------------- /cembed.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2020 rxi 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a copy 5 | ** of this software and associated documentation files (the "Software"), to 6 | ** deal in the Software without restriction, including without limitation the 7 | ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | ** sell copies of the Software, and to permit persons to whom the Software is 9 | ** furnished to do so, subject to the following conditions: 10 | ** 11 | ** The above copyright notice and this permission notice shall be included in 12 | ** all copies or substantial portions of the Software. 13 | ** 14 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | ** IN THE SOFTWARE. 21 | **/ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #define VERSION "v0.3" 30 | #define MAX_FILENAME 256 31 | 32 | typedef struct { 33 | FILE *fp; 34 | unsigned char buf[4096]; 35 | int idx; 36 | int eof_idx; 37 | } BufferedFile; 38 | 39 | 40 | static BufferedFile bf_writer(FILE *fp) { 41 | BufferedFile bf = { .fp = fp }; 42 | return bf; 43 | } 44 | 45 | 46 | static BufferedFile bf_reader(FILE *fp) { 47 | BufferedFile bf = { .fp = fp, .idx = sizeof(bf.buf), .eof_idx = -1 }; 48 | return bf; 49 | } 50 | 51 | 52 | static void bf_flush(BufferedFile *bf) { 53 | fwrite(bf->buf, 1, bf->idx, bf->fp); 54 | bf->idx = 0; 55 | } 56 | 57 | 58 | static void bf_write_byte(BufferedFile *bf, char b) { 59 | bf->buf[bf->idx++] = b; 60 | if (bf->idx == sizeof(bf->buf)) { bf_flush(bf); } 61 | } 62 | 63 | 64 | static int bf_read_byte(BufferedFile *bf) { 65 | if (bf->idx == sizeof(bf->buf)) { 66 | int n = fread(bf->buf, 1, sizeof(bf->buf), bf->fp); 67 | if (n != sizeof(bf->buf)) { bf->eof_idx = n; } 68 | bf->idx = 0; 69 | } 70 | if (bf->idx == bf->eof_idx) { return EOF; } 71 | return bf->buf[bf->idx++]; 72 | } 73 | 74 | 75 | static void error(const char *fmt, ...) { 76 | va_list vp; 77 | fprintf(stderr, "Error: "); 78 | va_start(vp, fmt); 79 | vfprintf(stderr, fmt, vp); 80 | va_end(vp); 81 | fprintf(stderr, "\n"); 82 | exit(EXIT_FAILURE); 83 | } 84 | 85 | 86 | static void safename(char *dst, const char *filename) { 87 | const char *p = filename; 88 | char *q = dst; 89 | while (*p) { 90 | if (isalpha(*p) || isdigit(*p)) { 91 | *q = *p; 92 | } else { 93 | *q = '_'; 94 | } 95 | q++; p++; 96 | } 97 | *q = '\0'; 98 | } 99 | 100 | 101 | static void write_byte_string(BufferedFile *bf, unsigned char n) { 102 | if (n >= 100) { bf_write_byte(bf, '0' + (n / 100) % 10); } 103 | if (n >= 10) { bf_write_byte(bf, '0' + (n / 10) % 10); } 104 | bf_write_byte(bf, '0' + n % 10); 105 | } 106 | 107 | 108 | static void write_embedded(FILE *fp, const char *filename, 109 | const char *varprefix, int nostatic, int zerobyte) 110 | { 111 | FILE *infp = fopen(filename, "rb"); 112 | if (!infp) { 113 | error("failed to open file '%s'", filename); 114 | } 115 | 116 | char varname[MAX_FILENAME]; 117 | if (strlen(filename) >= MAX_FILENAME) { 118 | error("filename too long"); 119 | } 120 | safename(varname, filename); 121 | 122 | if (!nostatic) { fprintf(fp, "static "); } 123 | fprintf(fp, "unsigned char %s%s[] = {", varprefix, varname); 124 | BufferedFile inbf = bf_reader(infp); 125 | BufferedFile bf = bf_writer(fp); 126 | int n = 0; 127 | 128 | for (;;) { 129 | int chr = bf_read_byte(&inbf); 130 | if (chr == EOF) { break; } 131 | if (n > 0) { bf_write_byte(&bf, ','); } 132 | if (n % 20 == 0) { bf_write_byte(&bf, '\n'); } 133 | write_byte_string(&bf, chr); 134 | n++; 135 | } 136 | 137 | bf_flush(&bf); 138 | if (zerobyte) { fprintf(fp, ",0"); } 139 | fprintf(fp, "\n};\n\n"); 140 | 141 | fclose(infp); 142 | } 143 | 144 | 145 | static void print_help(void) { 146 | printf( 147 | "Usage: cembed [OPTION]... [FILE]...\n" 148 | "Create C header with file data embedded in char arrays\n" 149 | "\n" 150 | " -o output file\n" 151 | " -p prefix to place before variable names\n" 152 | " -s omit `static` keyword\n" 153 | " -z add zero byte to end of array\n" 154 | " -t create table of { filename, data, size }\n" 155 | " -h display this help message\n" 156 | " -v display version number\n"); 157 | } 158 | 159 | 160 | int main(int argc, char **argv) { 161 | char **arg = argv + 1; 162 | char **arg_end = argv + argc; 163 | 164 | /* defaults */ 165 | const char *outfile = NULL; 166 | const char *prefix = ""; 167 | const char *tablename = NULL; 168 | int zerobyte = 0; 169 | int nostatic = 0; 170 | 171 | /* handle options */ 172 | while (arg != arg_end && (*arg)[0] == '-') { 173 | switch ((*arg)[1]) { 174 | case 'h': 175 | print_help(); 176 | exit(EXIT_SUCCESS); 177 | break; 178 | 179 | case 'v': 180 | printf("cembed " VERSION "\n"); 181 | exit(EXIT_SUCCESS); 182 | break; 183 | 184 | case 's': 185 | nostatic = 1; 186 | break; 187 | 188 | case 'z': 189 | zerobyte = 1; 190 | break; 191 | 192 | case 't': 193 | arg++; 194 | if (arg == arg_end) { error("expected name after option '-t'"); } 195 | tablename = *arg; 196 | break; 197 | 198 | case 'o': 199 | arg++; 200 | if (arg == arg_end) { error("expected filename after option '-o'"); } 201 | outfile = *arg; 202 | break; 203 | 204 | case 'p': 205 | arg++; 206 | if (arg == arg_end) { error("expected prefix after option '-p'"); } 207 | prefix = *arg; 208 | break; 209 | 210 | default: 211 | error("invalid option '%s'", *arg); 212 | break; 213 | } 214 | 215 | arg++; 216 | } 217 | 218 | /* no file arguments: print help */ 219 | if (arg == arg_end) { 220 | print_help(); 221 | exit(EXIT_SUCCESS); 222 | } 223 | 224 | /* open output */ 225 | FILE *fp = outfile ? fopen(outfile, "wb") : stdout; 226 | if (!fp) { error("failed to open output file '%s'", outfile); } 227 | 228 | /* write files */ 229 | for (char **a = arg; a < arg_end; a++) { 230 | write_embedded(fp, *a, prefix, nostatic, zerobyte); 231 | } 232 | 233 | /* write table */ 234 | if (tablename) { 235 | if (!nostatic) { fprintf(fp, "static "); } 236 | fprintf(fp, "struct { char *filename; unsigned char *data; int size; } "); 237 | fprintf(fp, "%s[] = {\n", tablename); 238 | for (char **a = arg; a < arg_end; a++) { 239 | char varname[MAX_FILENAME]; 240 | safename(varname, *a); 241 | fprintf(fp, "{ \"%s\", %s, (int) sizeof(%s) ", *a, varname, varname); 242 | if (zerobyte) { fprintf(fp, "- 1 "); } 243 | fprintf(fp, "},\n"); 244 | } 245 | fprintf(fp, "{ 0 }\n"); 246 | fprintf(fp, "};\n"); 247 | } 248 | 249 | /* clean up */ 250 | if (fp != stdout) { fclose(fp); } 251 | return EXIT_SUCCESS; 252 | } 253 | --------------------------------------------------------------------------------