1 /*
2  * This file is part of RGBDS.
3  *
4  * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
5  *
6  * SPDX-License-Identifier: MIT
7  */
8 
9 #include <assert.h>
10 #include <inttypes.h>
11 #include <limits.h>
12 #include <stdbool.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 
21 #include "link/object.h"
22 #include "link/symbol.h"
23 #include "link/section.h"
24 #include "link/assign.h"
25 #include "link/patch.h"
26 #include "link/output.h"
27 
28 #include "extern/getopt.h"
29 
30 #include "error.h"
31 #include "platform.h"
32 #include "version.h"
33 
34 bool isDmgMode;               /* -d */
35 char       *linkerScriptName; /* -l */
36 char const *mapFileName;      /* -m */
37 char const *symFileName;      /* -n */
38 char const *overlayFileName;  /* -O */
39 char const *outputFileName;   /* -o */
40 uint8_t padValue;             /* -p */
41 // Setting these three to 0 disables the functionality
42 uint16_t scrambleROMX = 0;    /* -S */
43 uint8_t scrambleWRAMX = 0;
44 uint8_t scrambleSRAM = 0;
45 bool is32kMode;               /* -t */
46 bool beVerbose;               /* -v */
47 bool isWRA0Mode;              /* -w */
48 bool disablePadding;          /* -x */
49 
50 static uint32_t nbErrors = 0;
51 
52 /***** Helper function to dump a file stack to stderr *****/
53 
dumpFileStack(struct FileStackNode const * node)54 char const *dumpFileStack(struct FileStackNode const *node)
55 {
56 	char const *lastName;
57 
58 	if (node->parent) {
59 		lastName = dumpFileStack(node->parent);
60 		/* REPT nodes use their parent's name */
61 		if (node->type != NODE_REPT)
62 			lastName = node->name;
63 		fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName);
64 		if (node->type == NODE_REPT) {
65 			for (uint32_t i = 0; i < node->reptDepth; i++)
66 				fprintf(stderr, "::REPT~%" PRIu32, node->iters[i]);
67 		}
68 	} else {
69 		assert(node->type != NODE_REPT);
70 		lastName = node->name;
71 		fputs(lastName, stderr);
72 	}
73 
74 	return lastName;
75 }
76 
warning(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)77 void warning(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
78 {
79 	va_list ap;
80 
81 	fputs("warning: ", stderr);
82 	if (where) {
83 		dumpFileStack(where);
84 		fprintf(stderr, "(%" PRIu32 "): ", lineNo);
85 	}
86 	va_start(ap, fmt);
87 	vfprintf(stderr, fmt, ap);
88 	va_end(ap);
89 	putc('\n', stderr);
90 }
91 
error(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)92 void error(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
93 {
94 	va_list ap;
95 
96 	fputs("error: ", stderr);
97 	if (where) {
98 		dumpFileStack(where);
99 		fprintf(stderr, "(%" PRIu32 "): ", lineNo);
100 	}
101 	va_start(ap, fmt);
102 	vfprintf(stderr, fmt, ap);
103 	va_end(ap);
104 	putc('\n', stderr);
105 
106 	if (nbErrors != UINT32_MAX)
107 		nbErrors++;
108 }
109 
argErr(char flag,char const * fmt,...)110 void argErr(char flag, char const *fmt, ...)
111 {
112 	va_list ap;
113 
114 	fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
115 	va_start(ap, fmt);
116 	vfprintf(stderr, fmt, ap);
117 	va_end(ap);
118 	putc('\n', stderr);
119 
120 	if (nbErrors != UINT32_MAX)
121 		nbErrors++;
122 }
123 
fatal(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)124 _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
125 {
126 	va_list ap;
127 
128 	fputs("FATAL: ", stderr);
129 	if (where) {
130 		dumpFileStack(where);
131 		fprintf(stderr, "(%" PRIu32 "): ", lineNo);
132 	}
133 	va_start(ap, fmt);
134 	vfprintf(stderr, fmt, ap);
135 	va_end(ap);
136 	putc('\n', stderr);
137 
138 	if (nbErrors != UINT32_MAX)
139 		nbErrors++;
140 
141 	fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors,
142 		nbErrors == 1 ? "" : "s");
143 	exit(1);
144 }
145 
openFile(char const * fileName,char const * mode)146 FILE *openFile(char const *fileName, char const *mode)
147 {
148 	if (!fileName)
149 		return NULL;
150 
151 	FILE *file;
152 	if (strcmp(fileName, "-") != 0)
153 		file = fopen(fileName, mode);
154 	else if (mode[0] == 'r')
155 		file = fdopen(0, mode);
156 	else
157 		file = fdopen(1, mode);
158 
159 	if (!file)
160 		err("Could not open file \"%s\"", fileName);
161 
162 	return file;
163 }
164 
165 /* Short options */
166 static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx";
167 
168 /*
169  * Equivalent long options
170  * Please keep in the same order as short opts
171  *
172  * Also, make sure long opts don't create ambiguity:
173  * A long opt's name should start with the same letter as its short opt,
174  * except if it doesn't create any ambiguity (`verbose` versus `version`).
175  * This is because long opt matching, even to a single char, is prioritized
176  * over short opt matching
177  */
178 static struct option const longopts[] = {
179 	{ "dmg",           no_argument,       NULL, 'd' },
180 	{ "linkerscript",  required_argument, NULL, 'l' },
181 	{ "map",           required_argument, NULL, 'm' },
182 	{ "sym",           required_argument, NULL, 'n' },
183 	{ "overlay",       required_argument, NULL, 'O' },
184 	{ "output",        required_argument, NULL, 'o' },
185 	{ "pad",           required_argument, NULL, 'p' },
186 	{ "scramble",      required_argument, NULL, 'S' },
187 	{ "smart",         required_argument, NULL, 's' },
188 	{ "tiny",          no_argument,       NULL, 't' },
189 	{ "version",       no_argument,       NULL, 'V' },
190 	{ "verbose",       no_argument,       NULL, 'v' },
191 	{ "wramx",         no_argument,       NULL, 'w' },
192 	{ "nopad",         no_argument,       NULL, 'x' },
193 	{ NULL,            no_argument,       NULL, 0   }
194 };
195 
196 /**
197  * Prints the program's usage to stdout.
198  */
printUsage(void)199 static void printUsage(void)
200 {
201 	fputs(
202 "Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
203 "               [-O overlay_file] [-o out_file] [-p pad_value]\n"
204 "               [-S spec] [-s symbol] <file> ...\n"
205 "Useful options:\n"
206 "    -l, --linkerscript <path>  set the input linker script\n"
207 "    -m, --map <path>           set the output map file\n"
208 "    -n, --sym <path>           set the output symbol list file\n"
209 "    -o, --output <path>        set the output file\n"
210 "    -p, --pad <value>          set the value to pad between sections with\n"
211 "    -x, --nopad                disable padding of output binary\n"
212 "    -V, --version              print RGBLINK version and exits\n"
213 "\n"
214 "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n",
215 	      stderr);
216 }
217 
218 /**
219  * Cleans up what has been done
220  * Mostly here to please tools such as `valgrind` so actual errors can be seen
221  */
cleanup(void)222 static void cleanup(void)
223 {
224 	obj_Cleanup();
225 }
226 
227 enum ScrambledRegion {
228 	SCRAMBLE_ROMX,
229 	SCRAMBLE_SRAM,
230 	SCRAMBLE_WRAMX,
231 
232 	SCRAMBLE_UNK, // Used for errors
233 };
234 
235 struct {
236 	char const *name;
237 	uint16_t max;
238 } scrambleSpecs[SCRAMBLE_UNK] = {
239 	[SCRAMBLE_ROMX]  = { "romx",  65535 },
240 	[SCRAMBLE_SRAM]  = { "sram",  255 },
241 	[SCRAMBLE_WRAMX] = { "wramx", 7},
242 };
243 
parseScrambleSpec(char const * spec)244 static void parseScrambleSpec(char const *spec)
245 {
246 	// Skip any leading whitespace
247 	spec += strspn(spec, " \t");
248 
249 	// The argument to `-S` should be a comma-separated list of sections followed by an '='
250 	// indicating their scramble limit.
251 	while (spec) {
252 		// Invariant: we should not be pointing at whitespace at this point
253 		assert(*spec != ' ' && *spec != '\t');
254 
255 		// Remember where the region's name begins and ends
256 		char const *regionName = spec;
257 		size_t regionNameLen = strcspn(spec, "=, \t");
258 		// Length of region name string slice for printing, truncated if too long
259 		int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen;
260 
261 		// If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assert
262 		if (regionNameLen == 0) {
263 			argErr('S', "Missing region name");
264 
265 			if (*spec == '\0')
266 				break;
267 			if (*spec == '=') // Skip the limit, too
268 				spec = strchr(&spec[1], ','); // Skip to next comma, if any
269 			goto next;
270 		}
271 
272 		// Find the next non-blank char after the region name's end
273 		spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
274 		if (*spec != '\0' && *spec != ',' && *spec != '=') {
275 			argErr('S', "Unexpected '%c' after region name \"%.*s\"",
276 			       regionNamePrintLen, regionName);
277 			// Skip to next ',' or '=' (or NUL) and keep parsing
278 			spec += 1 + strcspn(&spec[1], ",=");
279 		}
280 
281 		// Now, determine which region type this is
282 		enum ScrambledRegion region = 0;
283 
284 		while (region < SCRAMBLE_UNK) {
285 			// If the strings match (case-insensitively), we got it!
286 			// It's OK not to use `strncasecmp` because `regionName` is still
287 			// NUL-terminated, since the encompassing spec is.
288 			if (!strcasecmp(scrambleSpecs[region].name, regionName))
289 				break;
290 			region++;
291 		}
292 
293 		if (region == SCRAMBLE_UNK)
294 			argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName);
295 
296 		if (*spec == '=') {
297 			spec++; // `strtoul` will skip the whitespace on its own
298 			unsigned long limit;
299 			char *endptr;
300 
301 			if (*spec == '\0' || *spec == ',') {
302 				argErr('S', "Empty limit for region \"%.*s\"",
303 				       regionNamePrintLen, regionName);
304 				goto next;
305 			}
306 			limit = strtoul(spec, &endptr, 10);
307 			endptr += strspn(endptr, " \t");
308 			if (*endptr != '\0' && *endptr != ',') {
309 				argErr('S', "Invalid non-numeric limit for region \"%.*s\"",
310 				       regionNamePrintLen, regionName);
311 				endptr = strchr(endptr, ',');
312 			}
313 			spec = endptr;
314 
315 			if (region != SCRAMBLE_UNK && limit >= scrambleSpecs[region].max) {
316 				argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16,
317 				       regionNamePrintLen, regionName, scrambleSpecs[region].max);
318 				limit = scrambleSpecs[region].max;
319 			}
320 
321 			switch (region) {
322 			case SCRAMBLE_ROMX:
323 				scrambleROMX = limit;
324 				break;
325 			case SCRAMBLE_SRAM:
326 				scrambleSRAM = limit;
327 				break;
328 			case SCRAMBLE_WRAMX:
329 				scrambleWRAMX = limit;
330 				break;
331 			case SCRAMBLE_UNK: // The error has already been reported, do nothing
332 				break;
333 			}
334 		} else if (region == SCRAMBLE_WRAMX) {
335 			// Only WRAMX can be implied, since ROMX and SRAM size may vary
336 			scrambleWRAMX = 7;
337 		} else {
338 			argErr('S', "Cannot imply limit for region \"%.*s\"",
339 			       regionNamePrintLen, regionName);
340 		}
341 
342 next:
343 		if (spec) {
344 			assert(*spec == ',' || *spec == '\0');
345 			if (*spec == ',')
346 				spec += 1 + strspn(&spec[1], " \t");
347 			if (*spec == '\0')
348 				break;
349 		}
350 	}
351 }
352 
main(int argc,char * argv[])353 int main(int argc, char *argv[])
354 {
355 	int optionChar;
356 	char *endptr; /* For error checking with `strtoul` */
357 	unsigned long value; /* For storing `strtoul`'s return value */
358 
359 	/* Parse options */
360 	while ((optionChar = musl_getopt_long_only(argc, argv, optstring,
361 						   longopts, NULL)) != -1) {
362 		switch (optionChar) {
363 		case 'd':
364 			isDmgMode = true;
365 			isWRA0Mode = true;
366 			break;
367 		case 'l':
368 			linkerScriptName = musl_optarg;
369 			break;
370 		case 'm':
371 			mapFileName = musl_optarg;
372 			break;
373 		case 'n':
374 			symFileName = musl_optarg;
375 			break;
376 		case 'O':
377 			overlayFileName = musl_optarg;
378 			break;
379 		case 'o':
380 			outputFileName = musl_optarg;
381 			break;
382 		case 'p':
383 			value = strtoul(musl_optarg, &endptr, 0);
384 			if (musl_optarg[0] == '\0' || *endptr != '\0') {
385 				argErr('p', "");
386 				value = 0xFF;
387 			}
388 			if (value > 0xFF) {
389 				argErr('p', "Argument for 'p' must be a byte (between 0 and 0xFF)");
390 				value = 0xFF;
391 			}
392 			padValue = value;
393 			break;
394 		case 'S':
395 			parseScrambleSpec(musl_optarg);
396 			break;
397 		case 's':
398 			/* FIXME: nobody knows what this does, figure it out */
399 			(void)musl_optarg;
400 			warning(NULL, 0, "Nobody has any idea what `-s` does");
401 			break;
402 		case 't':
403 			is32kMode = true;
404 			break;
405 		case 'V':
406 			printf("rgblink %s\n", get_package_version_string());
407 			exit(0);
408 		case 'v':
409 			beVerbose = true;
410 			break;
411 		case 'w':
412 			isWRA0Mode = true;
413 			break;
414 		case 'x':
415 			disablePadding = true;
416 			/* implies tiny mode */
417 			is32kMode = true;
418 			break;
419 		default:
420 			printUsage();
421 			exit(1);
422 		}
423 	}
424 
425 	int curArgIndex = musl_optind;
426 
427 	/* If no input files were specified, the user must have screwed up */
428 	if (curArgIndex == argc) {
429 		fputs("FATAL: no input files\n", stderr);
430 		printUsage();
431 		exit(1);
432 	}
433 
434 	/* Patch the size array depending on command-line options */
435 	if (!is32kMode)
436 		maxsize[SECTTYPE_ROM0] = 0x4000;
437 	if (!isWRA0Mode)
438 		maxsize[SECTTYPE_WRAM0] = 0x1000;
439 
440 	/* Patch the bank ranges array depending on command-line options */
441 	if (isDmgMode)
442 		bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
443 
444 	/* Read all object files first, */
445 	for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
446 		obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
447 
448 	/* then process them, */
449 	obj_DoSanityChecks();
450 	assign_AssignSections();
451 	obj_CheckAssertions();
452 	assign_Cleanup();
453 
454 	/* and finally output the result. */
455 	patch_ApplyPatches();
456 	if (nbErrors) {
457 		fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
458 			nbErrors, nbErrors == 1 ? "" : "s");
459 		exit(1);
460 	}
461 	out_WriteFiles();
462 
463 	/* Do cleanup before quitting, though. */
464 	cleanup();
465 }
466