ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

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 }