bin2h.c (9126B)
1 // by 8bitbubsy 2 3 #ifdef _WIN32 4 #define WIN32_LEAN_AND_MEAN 5 #include <windows.h> 6 #include <shlwapi.h> 7 #endif 8 #include <stdio.h> 9 #include <string.h> // stricmp() 10 #include <stdlib.h> // atoi() 11 #include <ctype.h> // toupper() 12 #include <stdint.h> 13 #include <stdbool.h> 14 15 // if compiling for Windows, you need to link shlwapi.lib (for PathRemoveFileSpecW()) 16 17 #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) 18 19 #define IO_BUF_SIZE 4096 20 #define MAX_NAME_LEN 64 21 #define DATA_NAME "hdata" // this has to be a legal C variable name 22 #define BYTES_PER_LINE 24 23 #define MAX_MEGABYTES_IN 8 24 #define MIN_BYTES_PER_LINE 1 25 #define MAX_BYTES_PER_LINE 64 26 27 static const char hex2ch[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; 28 static char fileNameOut[MAX_NAME_LEN + 8], dataType[32]; 29 static char dataName[MAX_NAME_LEN + 1], dataNameUpper[MAX_NAME_LEN + 1]; 30 static bool useConstFlag = true, addZeroFlag; 31 static size_t fileSize, bytesPerLine = BYTES_PER_LINE, dataNameLen; 32 33 #ifdef _WIN32 34 static void setDirToExecutablePath(void); 35 #endif 36 37 static void setNewDataName(const char *name); 38 static bool handleArguments(int argc, char *argv[]); 39 static bool compareFileNames(const char *path1, const char *path2); 40 static bool setAndTestFileSize(FILE *f); 41 static bool writeHeader(FILE *f); 42 43 int main(int argc, char *argv[]) 44 { 45 uint8_t byte, readBuffer[IO_BUF_SIZE], writeBuffer[IO_BUF_SIZE * 6]; // don't mess with the sizes here 46 size_t lineEntry, readLength, writeLength, bytesWritten; 47 FILE *fIn, *fOut; 48 49 #ifdef _WIN32 // path is not set to cwd in XP (with VS2017 runtime) for some reason 50 setDirToExecutablePath(); 51 #endif 52 53 strcpy(dataType, "uint8_t"); 54 setNewDataName(DATA_NAME); 55 56 printf("bin2h v0.3 - by 8bitbubsy 2018-2020 - Converts a binary to a C include file\n"); 57 printf("===========================================================================\n"); 58 59 // handle input arguments 60 if (!handleArguments(argc, argv)) 61 return -1; 62 63 // test if the output file is the same as the input file (illegal) 64 if (compareFileNames(argv[1], fileNameOut)) 65 { 66 printf("Error: Output file can't be the same as input file!\n"); 67 return 1; 68 } 69 70 // open input file 71 fIn = fopen(argv[1], "rb"); 72 if (fIn == NULL) 73 { 74 printf("Error: Could not open input file for reading!\n"); 75 return 1; 76 } 77 78 // get filesize and test if it's 0 or too high 79 if (!setAndTestFileSize(fIn)) 80 { 81 fclose(fIn); 82 return 1; 83 } 84 85 // open output file 86 fOut = fopen(fileNameOut, "w"); 87 if (fOut == NULL) 88 { 89 fclose(fIn); 90 91 printf("Error: Could not open data.h for writing!\n"); 92 return 1; 93 } 94 95 // write .h header 96 if (!writeHeader(fOut)) 97 goto IOError; 98 99 // write main data 100 101 printf("Reading/writing from/to files...\n"); 102 103 bytesWritten = 0; 104 while (!feof(fIn)) 105 { 106 readLength = fread(readBuffer, 1, IO_BUF_SIZE, fIn); 107 108 writeLength = 0; 109 for (size_t i = 0; i < readLength; i++) 110 { 111 lineEntry = bytesWritten % bytesPerLine; 112 113 // put tab if we're at the start of a line 114 if (lineEntry == 0) 115 writeBuffer[writeLength++] = '\t'; 116 117 // write table entry 118 119 byte = readBuffer[i]; 120 if (bytesWritten == fileSize-1) 121 { 122 // last byte in table 123 124 if (addZeroFlag) // fileSize is increased by one if addZeroFlag = true 125 { 126 writeBuffer[writeLength++] = '\0'; 127 } 128 else 129 { 130 // "0xXX" 131 writeBuffer[writeLength++] = '0'; 132 writeBuffer[writeLength++] = 'x'; 133 writeBuffer[writeLength++] = hex2ch[byte >> 4]; 134 writeBuffer[writeLength++] = hex2ch[byte & 15]; 135 } 136 } 137 else 138 { 139 // "0xXX," 140 writeBuffer[writeLength++] = '0'; 141 writeBuffer[writeLength++] = 'x'; 142 writeBuffer[writeLength++] = hex2ch[byte >> 4]; 143 writeBuffer[writeLength++] = hex2ch[byte & 15]; 144 writeBuffer[writeLength++] = ','; 145 } 146 147 // put linefeed if we're at the end of a line (or at the last byte entry) 148 if (lineEntry == bytesPerLine-1 || bytesWritten == fileSize-1) 149 writeBuffer[writeLength++] = '\n'; 150 151 bytesWritten++; 152 } 153 154 if (fwrite(writeBuffer, 1, writeLength, fOut) != writeLength) 155 goto IOError; 156 } 157 158 if (bytesWritten != fileSize) 159 goto IOError; 160 161 // write .h footer 162 if (fprintf(fOut, "};\n") < 0) goto IOError; 163 164 fclose(fOut); 165 fclose(fIn); 166 167 printf("%d bytes sucessfully written to \"%s\".\n", fileSize, fileNameOut); 168 return 0; 169 170 IOError: 171 fclose(fOut); 172 fclose(fIn); 173 174 printf("Error: General I/O fault during reading/writing!\n"); 175 return 1; 176 } 177 178 #ifdef _WIN32 179 static void setDirToExecutablePath(void) 180 { 181 WCHAR path[MAX_PATH]; 182 183 GetModuleFileNameW(GetModuleHandleW(NULL), path, MAX_PATH); 184 PathRemoveFileSpecW(path); 185 SetCurrentDirectoryW(path); 186 187 // we don't care if this fails or not, so no error checking for now 188 } 189 #endif 190 191 static bool dataNameIsValid(const char *s, size_t len) 192 { 193 bool charIsValid; 194 195 for (size_t i = 0; i < len; i++) 196 { 197 if (i == 0 && (s[i] >= '0' && s[i] <= '9')) 198 return false; // a C variable can't start with a digit 199 200 charIsValid = (s[i] >= '0' && s[i] <= '9') || 201 (s[i] >= 'a' && s[i] <= 'z') || 202 (s[i] >= 'A' && s[i] <= 'Z'); 203 204 if (!charIsValid) 205 return false; 206 } 207 208 return true; 209 } 210 211 static void setNewDataName(const char *name) 212 { 213 size_t i; 214 215 strncpy(dataName, name, sizeof (dataName)-1); 216 dataNameLen = strlen(dataName); 217 sprintf(fileNameOut, "%s.h", dataName); 218 219 // maker upper case version of dataName 220 for (i = 0; i < dataNameLen; i++) 221 dataNameUpper[i] = (char)toupper(dataName[i]); 222 dataNameUpper[i] = '\0'; 223 224 // test if dataName is valid for use in a C header 225 if (!dataNameIsValid(dataName, dataNameLen)) 226 { 227 strcpy(dataName, DATA_NAME); 228 printf("Warning: Data name was illegal and was set to default (%s).\n", DATA_NAME); 229 } 230 } 231 232 static bool handleArguments(int argc, char *argv[]) 233 { 234 if (argc < 2) 235 { 236 printf("Usage: bin2h <binary> [options]\n"); 237 printf("\n"); 238 printf("Options:\n"); 239 printf(" --no-const Don't declare data as const\n"); 240 printf(" --signed Declare data as signed instead of unsigned\n"); 241 printf(" --terminate Insert a zero after the data (also increases length constant by 1)\n"); 242 printf(" -n <name> Set data name (max %d chars, a-zA-Z0-9_, default = %s)\n", MAX_NAME_LEN, DATA_NAME); 243 printf(" -b <num> Set amount of bytes per line in data table (%d..%d, default = %d)\n", 244 MIN_BYTES_PER_LINE, MAX_BYTES_PER_LINE, BYTES_PER_LINE); 245 246 return false; 247 } 248 else 249 { 250 for (int32_t i = 0; i < argc; i++) 251 { 252 if (!_stricmp(argv[i], "--no-const")) 253 { 254 useConstFlag = false; 255 } 256 if (!_stricmp(argv[i], "--terminate")) 257 { 258 addZeroFlag = true; 259 } 260 if (!_stricmp(argv[i], "--signed")) 261 { 262 strcpy(dataType, "int8_t"); 263 } 264 else if (!_stricmp(argv[i], "-n")) 265 { 266 if (i < argc-1) 267 setNewDataName(argv[i+1]); 268 } 269 else if (!_stricmp(argv[i], "-b")) 270 { 271 if (i < argc-1) 272 bytesPerLine = CLAMP(atoi(argv[i+1]), MIN_BYTES_PER_LINE, MAX_BYTES_PER_LINE); 273 } 274 } 275 } 276 277 return true; 278 } 279 280 // compares two filenames (mixed full path or not, case insensitive) 281 static bool compareFileNames(const char *path1, const char *path2) 282 { 283 char sep; 284 const char *p1, *p2; 285 size_t l1, l2; 286 287 if (path1 == NULL || path2 == NULL) 288 return false; // error 289 290 #ifdef _WIN32 291 sep = '\\'; 292 #else 293 sep = '/'; 294 #endif 295 296 // set pointers to first occurrences of separator in paths 297 p1 = strrchr(path1, sep); 298 p2 = strrchr(path2, sep); 299 300 if (p1 == NULL) 301 { 302 // separator not found, point to start of path 303 p1 = path1; 304 l1 = strlen(p1); 305 } 306 else 307 { 308 // separator found, increase pointer by 1 if we have room 309 l1 = strlen(p1); 310 if (l1 > 0) 311 { 312 p1++; 313 l1--; 314 } 315 } 316 317 if (p2 == NULL) 318 { 319 // separator not found, point to start of path 320 p2 = path2; 321 l2 = strlen(p2); 322 } 323 else 324 { 325 // separator found, increase pointer by 1 if we have room 326 l2 = strlen(p2); 327 if (l2 > 0) 328 { 329 p2++; 330 l2--; 331 } 332 } 333 334 if (l1 != l2) 335 return false; // definitely not the same 336 337 // compare strings 338 for (size_t i = 0; i < l1; i++) 339 { 340 if (tolower(p1[i]) != tolower(p2[i])) 341 return false; // not the same 342 } 343 344 return true; // both filenames are the same 345 } 346 347 static bool setAndTestFileSize(FILE *f) 348 { 349 fseek(f, 0, SEEK_END); 350 fileSize = ftell(f); 351 rewind(f); 352 353 if (fileSize == 0) 354 { 355 printf("Error: Input file is empty!\n"); 356 return false; 357 } 358 359 if (fileSize > MAX_MEGABYTES_IN*(1024UL*1024)) 360 { 361 printf("Error: The input filesize is over %dMB. This program is meant for small files!\n", MAX_MEGABYTES_IN); 362 return false; 363 } 364 365 if (addZeroFlag) 366 fileSize++; // make room for zero 367 368 return true; 369 } 370 371 static bool writeHeader(FILE *f) 372 { 373 if (fprintf(f, "#pragma once\n") < 0) return false; 374 if (fprintf(f, "\n") < 0) return false; 375 if (fprintf(f, "#include <stdint.h>\n") < 0) return false; 376 if (fprintf(f, "\n") < 0) return false; 377 if (fprintf(f, "#define %s_LENGTH %d\n", dataNameUpper, fileSize) < 0) return false; 378 if (fprintf(f, "\n") < 0) return false; 379 380 if (useConstFlag) 381 { 382 if (fprintf(f, "const %s %s[%s_LENGTH] =\n", dataType, dataName, dataNameUpper) < 0) 383 return false; 384 } 385 else 386 { 387 if (fprintf(f, "%s %s[%s_LENGTH] =\n", dataType, dataName, dataNameUpper) < 0) 388 return false; 389 } 390 391 if (fprintf(f, "{\n") < 0) 392 return false; 393 394 return true; 395 }