shithub: rgbds

Download patch

ref: 5aea30f40d2f3354c76988384bcf70347a26d4a3
parent: 323738e7b8426e59aa753ca1f1d623d5c1f24368
parent: 09c9395ff8b23b5c7beb431d0228d5e2129d56d6
author: Eldred Habert <[email protected]>
date: Sun Nov 3 11:15:37 EST 2019

Merge pull request #434 from ISSOtm/rgblink_rewrite

Rewrite RGBLINK entirely

--- a/Makefile
+++ b/Makefile
@@ -32,10 +32,11 @@
 WARNFLAGS	:= -Wall
 
 # Overridable CFLAGS
-CFLAGS		:= -g
+CFLAGS		:= -g -O0
 # Non-overridable CFLAGS
-REALCFLAGS	:= ${CFLAGS} ${WARNFLAGS} -std=c99 -D_POSIX_C_SOURCE=200809L \
-		   -Iinclude -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
+REALCFLAGS	:= ${CFLAGS} ${WARNFLAGS} -std=c11 -D_POSIX_C_SOURCE=200809L \
+		   -D_DEFAULT_SOURCE -Iinclude \
+		   -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
 # Overridable LDFLAGS
 LDFLAGS		:=
 # Non-overridable LDFLAGS
@@ -73,20 +74,16 @@
 
 rgblink_obj := \
 	src/link/assign.o \
-	src/link/lexer.o \
-	src/link/library.o \
 	src/link/main.o \
-	src/link/mapfile.o \
 	src/link/object.o \
 	src/link/output.o \
 	src/link/patch.o \
-	src/link/parser.o \
 	src/link/script.o \
+	src/link/section.o \
 	src/link/symbol.o \
 	src/extern/err.o \
+	src/hashmap.o \
 	src/version.o
-
-src/link/lexer.o: src/link/parser.h
 
 rgbfix_obj := \
 	src/fix/main.o \
--- a/include/common.h
+++ b/include/common.h
@@ -9,7 +9,8 @@
 #ifndef RGBDS_COMMON_H
 #define RGBDS_COMMON_H
 
-#define RGBDS_OBJECT_VERSION_STRING "RGB6"
+#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
+#define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)6
 
 enum eBankCount {
 	BANK_COUNT_ROM0  = 1,
--- /dev/null
+++ b/include/hashmap.h
@@ -1,0 +1,68 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/* Generic hashmap implementation (C++ templates are calling...) */
+#ifndef RGBDS_LINK_HASHMAP_H
+#define RGBDS_LINK_HASHMAP_H
+
+#include <assert.h>
+
+#define HASH_NB_BITS 32
+#define HALF_HASH_NB_BITS 16
+_Static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
+#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
+
+/* HashMapEntry is internal, please do not attempt to use it */
+typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
+
+/**
+ * Adds an element to a hashmap.
+ * @warning Adding a new element with an already-present key will not cause an
+ *          error, this must be handled externally.
+ * @warning Inserting a NULL will make `hash_GetElement`'s return ambiguous!
+ * @param map The HashMap to add the element to
+ * @param key The key with which the element will be stored and retrieved
+ * @param element The element to add
+ * @return True if a collision occurred (for statistics)
+ */
+bool hash_AddElement(HashMap map, char const *key, void *element);
+
+/**
+ * Removes an element from a hashmap.
+ * @param map The HashMap to remove the element from
+ * @param key The key to search the element with
+ * @return True if the element was found and removed
+ */
+bool hash_RemoveElement(HashMap map, char const *key);
+
+/**
+ * Finds an element in a hashmap.
+ * @param map The map to consider the elements of
+ * @param key The key to search an element for
+ * @return A pointer to the element, or NULL if not found. (NULL can be returned
+ *         if such an element was added, but that sounds pretty silly.)
+ */
+void *hash_GetElement(HashMap const map, char const *key);
+
+/**
+ * Executes a function on each element in a hashmap.
+ * @param map The map to consider the elements of
+ * @param func The function to run. The first argument will be the element,
+ *                                  the second will be `arg`.
+ * @param arg An argument to be passed to all function calls
+ */
+void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
+
+/**
+ * Cleanly empties a hashmap from its contents.
+ * This does not `free` the data structure itself!
+ * @param map The map to empty
+ */
+void hash_EmptyMap(HashMap map);
+
+#endif /* RGBDS_LINK_HASHMAP_H */
--- a/include/helpers.h
+++ b/include/helpers.h
@@ -13,10 +13,12 @@
 	/* GCC or compatible */
 	#define noreturn_	__attribute__ ((noreturn))
 	#define unused_		__attribute__ ((unused))
+	#define trap_		__builtin_trap()
 #else
 	/* Unsupported, but no need to throw a fit */
 	#define noreturn_
 	#define unused_
+	#define trap_
 #endif
 
 #endif /* HELPERS_H */
--- a/include/link/assign.h
+++ b/include/link/assign.h
@@ -1,11 +1,12 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Assigning all sections a place */
 #ifndef RGBDS_LINK_ASSIGN_H
 #define RGBDS_LINK_ASSIGN_H
 
@@ -12,43 +13,17 @@
 #include <stdint.h>
 
 #include "common.h"
-#include "mylink.h"
 
-/* Bank numbers as seen by the linker */
-enum eBankDefine {
-	BANK_INDEX_ROM0  = 0,
-	BANK_INDEX_ROMX  = BANK_INDEX_ROM0  + BANK_COUNT_ROM0,
-	BANK_INDEX_WRAM0 = BANK_INDEX_ROMX  + BANK_COUNT_ROMX,
-	BANK_INDEX_WRAMX = BANK_INDEX_WRAM0 + BANK_COUNT_WRAM0,
-	BANK_INDEX_VRAM  = BANK_INDEX_WRAMX + BANK_COUNT_WRAMX,
-	BANK_INDEX_OAM   = BANK_INDEX_VRAM  + BANK_COUNT_VRAM,
-	BANK_INDEX_HRAM  = BANK_INDEX_OAM   + BANK_COUNT_OAM,
-	BANK_INDEX_SRAM  = BANK_INDEX_HRAM  + BANK_COUNT_HRAM,
-	BANK_INDEX_MAX   = BANK_INDEX_SRAM  + BANK_COUNT_SRAM
-};
+extern uint64_t nbSectionsToAssign;
 
-extern int32_t MaxBankUsed;
-extern int32_t MaxAvail[BANK_INDEX_MAX];
+/**
+ * Assigns all sections a slice of the address space
+ */
+void assign_AssignSections(void);
 
-int32_t area_Avail(int32_t bank);
-void AssignSections(void);
-void CreateSymbolTable(void);
-struct sSection *GetSectionByName(const char *name);
-int32_t IsSectionNameInUse(const char *name);
-void SetLinkerscriptName(char *tzLinkerscriptFile);
-int32_t IsSectionSameTypeBankAndAttrs(const char *name,
-				      enum eSectionType type, int32_t bank,
-				      int32_t org, int32_t align);
-uint32_t AssignSectionAddressAndBankByName(const char *name, uint32_t address,
-					   int32_t bank);
-
-int BankIndexIsROM0(int32_t bank);
-int BankIndexIsROMX(int32_t bank);
-int BankIndexIsWRAM0(int32_t bank);
-int BankIndexIsWRAMX(int32_t bank);
-int BankIndexIsVRAM(int32_t bank);
-int BankIndexIsOAM(int32_t bank);
-int BankIndexIsHRAM(int32_t bank);
-int BankIndexIsSRAM(int32_t bank);
+/**
+ * `free`s all assignment memory that was allocated.
+ */
+void assign_Cleanup(void);
 
 #endif /* RGBDS_LINK_ASSIGN_H */
--- a/include/link/library.h
+++ /dev/null
@@ -1,14 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#ifndef RGBDS_LINK_LIBRARY_H
-#define RGBDS_LINK_LIBRARY_H
-
-void AddNeededModules(void);
-
-#endif /* RGBDS_LINK_LIBRARY_H */
--- a/include/link/main.h
+++ b/include/link/main.h
@@ -1,17 +1,49 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Declarations that all modules use, as well as `main` and related */
 #ifndef RGBDS_LINK_MAIN_H
 #define RGBDS_LINK_MAIN_H
 
 #include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
 
-extern int32_t fillchar;
-extern char *smartlinkstartsymbol;
+/* Variables related to CLI options */
+extern bool isDmgMode;
+extern char const *linkerScriptName;
+extern char const *mapFileName;
+extern char const *symFileName;
+extern char const *overlayFileName;
+extern char const *outputFileName;
+extern uint8_t padValue;
+extern bool is32kMode;
+extern bool beVerbose;
+extern bool isWRA0Mode;
+
+/* Helper macro for printing verbose-mode messages */
+#define verbosePrint(...)   do { \
+					if (beVerbose) \
+						fprintf(stderr, __VA_ARGS__); \
+				} while (0)
+
+/**
+ * Opens a file if specified, and aborts on error.
+ * @param fileName The name of the file to open; if NULL, no file will be opened
+ * @param mode The mode to open the file with
+ * @return A pointer to a valid FILE structure, or NULL if fileName was NULL
+ */
+FILE *openFile(char const *fileName, char const *mode);
+
+#define closeFile(file) do { \
+				FILE *tmp = file; \
+				if (tmp) \
+					fclose(tmp); \
+			} while (0)
 
 #endif /* RGBDS_LINK_MAIN_H */
--- a/include/link/mapfile.h
+++ /dev/null
@@ -1,21 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#ifndef RGBDS_LINK_MAPFILE_H
-#define RGBDS_LINK_MAPFILE_H
-
-#include <stdint.h>
-
-void SetMapfileName(char *name);
-void SetSymfileName(char *name);
-void CloseMapfile(void);
-void MapfileWriteSection(const struct sSection *pSect);
-void MapfileInitBank(int32_t bank);
-void MapfileCloseBank(int32_t slack);
-
-#endif /* RGBDS_LINK_MAPFILE_H */
--- a/include/link/mylink.h
+++ /dev/null
@@ -1,68 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#ifndef RGBDS_LINK_LINK_H
-#define RGBDS_LINK_LINK_H
-
-#include <stdint.h>
-
-#include "linkdefs.h"
-
-extern int32_t options;
-
-#define OPT_TINY		0x01
-#define OPT_SMART_C_LINK	0x02
-#define OPT_OVERLAY		0x04
-#define OPT_CONTWRAM		0x08
-#define OPT_DMG_MODE		0x10
-
-struct sSection {
-	int32_t nBank;
-	int32_t nOrg;
-	int32_t nAlign;
-	uint8_t oAssigned;
-
-	char *pzName;
-	int32_t nByteSize;
-	enum eSectionType Type;
-	uint8_t *pData;
-	int32_t nNumberOfSymbols;
-	struct sSymbol **tSymbols;
-	struct sPatch *pPatches;
-	struct sSection *pNext;
-};
-
-struct sSymbol {
-	char *pzName;
-	enum eSymbolType Type;
-
-	/* The following 3 items only valid when Type!=SYM_IMPORT */
-	int32_t nSectionID; /* Internal to object.c */
-	struct sSection *pSection;
-	int32_t nOffset;
-
-	char *pzObjFileName; /* Object file where the symbol is located. */
-	char *pzFileName; /* Source file where the symbol was defined. */
-	uint32_t nFileLine; /* Line where the symbol was defined. */
-};
-
-struct sPatch {
-	char *pzFilename;
-	int32_t nLineNo;
-	int32_t nOffset;
-	enum ePatchType Type;
-	int32_t nRPNSize;
-	uint8_t *pRPN;
-	struct sPatch *pNext;
-	uint8_t oRelocPatch;
-};
-
-extern struct sSection *pSections;
-extern struct sSection *pLibSections;
-
-#endif /* RGBDS_LINK_LINK_H */
--- a/include/link/object.h
+++ b/include/link/object.h
@@ -1,14 +1,30 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Declarations related to processing of object (.o) files */
+
 #ifndef RGBDS_LINK_OBJECT_H
 #define RGBDS_LINK_OBJECT_H
 
-void obj_Readfile(char *tzObjectfile);
+/**
+ * Read an object (.o) file, and add its info to the data structures.
+ * @param fileName A path to the object file to be read
+ */
+void obj_ReadFile(char const *fileName);
+
+/**
+ * Perform validation on the object files' contents
+ */
+void obj_DoSanityChecks(void);
+
+/**
+ * `free`s all object memory that was allocated.
+ */
+void obj_Cleanup(void);
 
 #endif /* RGBDS_LINK_OBJECT_H */
--- a/include/link/output.h
+++ b/include/link/output.h
@@ -1,16 +1,28 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Outputting the result of linking */
 #ifndef RGBDS_LINK_OUTPUT_H
 #define RGBDS_LINK_OUTPUT_H
 
-void out_Setname(char *tzOutputfile);
-void out_SetOverlayname(char *tzOverlayfile);
-void Output(void);
+#include <stdint.h>
+
+#include "link/section.h"
+
+/**
+ * Registers a section for output.
+ * @param section The section to add
+ */
+void out_AddSection(struct Section const *section);
+
+/**
+ * Writes all output (bin, sym, map) files.
+ */
+void out_WriteFiles(void);
 
 #endif /* RGBDS_LINK_OUTPUT_H */
--- a/include/link/patch.h
+++ b/include/link/patch.h
@@ -1,17 +1,18 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Applying patches to SECTIONs */
 #ifndef RGBDS_LINK_PATCH_H
 #define RGBDS_LINK_PATCH_H
 
-#include <stdint.h>
-
-void Patch(void);
-extern int32_t nPC;
+/**
+ * Applies all SECTIONs' patches to them
+ */
+void patch_ApplyPatches(void);
 
 #endif /* RGBDS_LINK_PATCH_H */
--- a/include/link/script.h
+++ b/include/link/script.h
@@ -1,30 +1,36 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Parsing a linker script */
 #ifndef RGBDS_LINK_SCRIPT_H
 #define RGBDS_LINK_SCRIPT_H
 
 #include <stdint.h>
 
-#include "helpers.h"
+extern FILE *linkerScript;
 
-noreturn_ void script_fatalerror(const char *fmt, ...);
+struct SectionPlacement {
+	struct Section *section;
+	uint16_t org;
+	uint32_t bank;
+};
 
-void script_Parse(const char *path);
+extern uint64_t script_lineNo;
 
-void script_IncludeFile(const char *path);
-int32_t script_IncludeDepthGet(void);
-void script_IncludePop(void);
+/**
+ * Parses the linker script to return the next section constraint
+ * @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
+ */
+struct SectionPlacement *script_NextSection(void);
 
-void script_InitSections(void);
-void script_SetCurrentSectionType(const char *type, uint32_t bank);
-void script_SetAddress(uint32_t addr);
-void script_SetAlignment(uint32_t alignment);
-void script_OutputSection(const char *section_name);
+/**
+ * `free`s all assignment memory that was allocated.
+ */
+void script_Cleanup(void);
 
 #endif /* RGBDS_LINK_SCRIPT_H */
--- /dev/null
+++ b/include/link/section.h
@@ -1,0 +1,112 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/* Declarations manipulating symbols */
+#ifndef RGBDS_LINK_SECTION_H
+#define RGBDS_LINK_SECTION_H
+
+/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "link/main.h"
+
+#include "linkdefs.h"
+
+struct AttachedSymbol {
+	struct Symbol *symbol;
+	struct AttachedSymbol *next;
+};
+
+struct Patch {
+	char *fileName;
+	int32_t lineNo;
+	int32_t offset;
+	enum PatchType type;
+	int32_t rpnSize;
+	uint8_t *rpnExpression;
+};
+
+struct Section {
+	/* Info contained in the object files */
+	char *name;
+	uint16_t size;
+	enum SectionType type;
+	bool isAddressFixed;
+	uint16_t org;
+	bool isBankFixed;
+	uint32_t bank;
+	bool isAlignFixed;
+	uint16_t alignMask;
+	uint8_t *data; /* Array of size `size`*/
+	uint32_t nbPatches;
+	struct Patch *patches;
+	/* Extra info computed during linking */
+	struct Symbol **fileSymbols;
+	uint32_t nbSymbols;
+	struct Symbol const **symbols;
+};
+
+extern uint16_t startaddr[];
+extern uint16_t maxsize[];
+extern uint32_t bankranges[][2];
+extern char const * const typeNames[SECTTYPE_INVALID];
+
+/**
+ * Computes a memory region's end address (last byte), eg. 0x7FFF
+ * @return The address of the last byte in that memory region
+ */
+static inline uint16_t endaddr(enum SectionType type)
+{
+	return startaddr[type] + maxsize[type] - 1;
+}
+
+/**
+ * Computes a memory region's number of banks
+ * @return The number of banks, 1 for regions without banking
+ */
+static inline uint32_t nbbanks(enum SectionType type)
+{
+	return bankranges[type][1] - bankranges[type][0] + 1;
+}
+
+/*
+ * Execute a callback for each section currently registered.
+ * This is to avoid exposing the data structure in which sections are stored.
+ * @param callback The function to call for each structure;
+ *                 the first argument will be a pointer to the structure,
+ *                 the second argument will be the pointer `arg`.
+ * @param arg A pointer which will be passed to all calls to `callback`.
+ */
+void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
+
+/**
+ * Registers a section to be processed.
+ * @param section The section to register.
+ */
+void sect_AddSection(struct Section *section);
+
+/**
+ * Finds a section by its name.
+ * @param name The name of the section to look for
+ * @return A pointer to the section, or NULL if it wasn't found
+ */
+struct Section *sect_GetSection(char const *name);
+
+/**
+ * `free`s all section memory that was allocated.
+ */
+void sect_CleanupSections(void);
+
+/**
+ * Checks if all sections meet reasonable criteria, such as max size
+ */
+void sect_DoSanityChecks(void);
+
+#endif /* RGBDS_LINK_SECTION_H */
--- a/include/link/symbol.h
+++ b/include/link/symbol.h
@@ -1,21 +1,59 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+/* Declarations manipulating symbols */
 #ifndef RGBDS_LINK_SYMBOL_H
 #define RGBDS_LINK_SYMBOL_H
 
+/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
+
 #include <stdint.h>
 
-void sym_Init(void);
-void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
-		      char *tzObjFileName, char *tzFileName,
-		      uint32_t nFileLine);
-int32_t sym_GetValue(struct sPatch *pPatch, char *tzName);
-int32_t sym_GetBank(struct sPatch *pPatch, char *tzName);
+#include "linkdefs.h"
+
+struct Symbol {
+	/* Info contained in the object files */
+	char *name;
+	enum SymbolType type;
+	char const *objFileName;
+	char *fileName;
+	int32_t lineNo;
+	int32_t sectionID;
+	union {
+		int32_t offset;
+		int32_t value;
+	};
+	/* Extra info computed during linking */
+	struct Section *section;
+};
+
+/*
+ * Execute a callback for each symbol currently registered.
+ * This is done to avoid exposing the data structure in which symbol are stored.
+ * @param callback The function to call for each symbol;
+ *                 the first argument will be a pointer to the symbol,
+ *                 the second argument will be the pointer `arg`.
+ * @param arg A pointer which will be passed to all calls to `callback`.
+ */
+void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
+
+void sym_AddSymbol(struct Symbol *symbol);
+
+/**
+ * Finds a symbol in all the defined symbols.
+ * @param name The name of the symbol to look for
+ * @return A pointer to the symbol, or NULL if not found.
+ */
+struct Symbol *sym_GetSymbol(char const *name);
+
+/**
+ * `free`s all symbol memory that was allocated.
+ */
+void sym_CleanupSymbols(void);
 
 #endif /* RGBDS_LINK_SYMBOL_H */
--- a/include/linkdefs.h
+++ b/include/linkdefs.h
@@ -9,7 +9,7 @@
 #ifndef RGBDS_LINKDEFS_H
 #define RGBDS_LINKDEFS_H
 
-enum eRpnData {
+enum RPNCommand {
 	RPN_ADD		= 0x00,
 	RPN_SUB		= 0x01,
 	RPN_MUL		= 0x02,
@@ -46,28 +46,43 @@
 	RPN_SYM		= 0x81
 };
 
-enum eSectionType {
-	SECT_WRAM0	= 0x00,
-	SECT_VRAM	= 0x01,
-	SECT_ROMX	= 0x02,
-	SECT_ROM0	= 0x03,
-	SECT_HRAM	= 0x04,
-	SECT_WRAMX	= 0x05,
-	SECT_SRAM	= 0x06,
-	SECT_OAM	= 0x07
+enum SectionType {
+	SECTTYPE_WRAM0,
+	SECTTYPE_VRAM,
+	SECTTYPE_ROMX,
+	SECTTYPE_ROM0,
+	SECTTYPE_HRAM,
+	SECTTYPE_WRAMX,
+	SECTTYPE_SRAM,
+	SECTTYPE_OAM,
+
+	SECTTYPE_INVALID
 };
 
-enum eSymbolType {
-	SYM_LOCAL	= 0x00,
-	SYM_IMPORT	= 0x01,
-	SYM_EXPORT	= 0x02
+enum SymbolType {
+	SYMTYPE_LOCAL,
+	SYMTYPE_IMPORT,
+	SYMTYPE_EXPORT
 };
 
-enum ePatchType {
-	PATCH_BYTE	= 0x00,
-	PATCH_WORD_L	= 0x01,
-	PATCH_LONG_L	= 0x02,
-	PATCH_BYTE_JR	= 0x03
+enum PatchType {
+	PATCHTYPE_BYTE,
+	PATCHTYPE_WORD,
+	PATCHTYPE_LONG,
+	PATCHTYPE_JR,
+
+	PATCHTYPE_INVALID
 };
+
+/**
+ * Tells whether a section has data in its object file definition,
+ * depending on type.
+ * @param type The section's type
+ * @return `true` if the section's definition includes data
+ */
+static inline bool sect_HasData(enum SectionType type)
+{
+	return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
+}
 
 #endif /* RGBDS_LINKDEFS_H */
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -45,22 +45,22 @@
 	char *stype = NULL;
 
 	switch (secttype) {
-	case SECT_ROMX:
+	case SECTTYPE_ROMX:
 		stype = "ROMX";
 		minbank = BANK_MIN_ROMX;
 		maxbank = BANK_MAX_ROMX;
 		break;
-	case SECT_SRAM:
+	case SECTTYPE_SRAM:
 		stype = "SRAM";
 		minbank = BANK_MIN_SRAM;
 		maxbank = BANK_MAX_SRAM;
 		break;
-	case SECT_WRAMX:
+	case SECTTYPE_WRAMX:
 		stype = "WRAMX";
 		minbank = BANK_MIN_WRAMX;
 		maxbank = BANK_MAX_WRAMX;
 		break;
-	case SECT_VRAM:
+	case SECTTYPE_VRAM:
 		stype = "VRAM";
 		minbank = BANK_MIN_VRAM;
 		maxbank = BANK_MAX_VRAM;
@@ -1523,33 +1523,33 @@
 		}
 ;
 
-sectiontype	: T_SECT_WRAM0	{ $$ = SECT_WRAM0; }
-		| T_SECT_VRAM	{ $$ = SECT_VRAM; }
-		| T_SECT_ROMX	{ $$ = SECT_ROMX; }
-		| T_SECT_ROM0	{ $$ = SECT_ROM0; }
-		| T_SECT_HRAM	{ $$ = SECT_HRAM; }
-		| T_SECT_WRAMX	{ $$ = SECT_WRAMX; }
-		| T_SECT_SRAM	{ $$ = SECT_SRAM; }
-		| T_SECT_OAM	{ $$ = SECT_OAM; }
+sectiontype	: T_SECT_WRAM0	{ $$ = SECTTYPE_WRAM0; }
+		| T_SECT_VRAM	{ $$ = SECTTYPE_VRAM; }
+		| T_SECT_ROMX	{ $$ = SECTTYPE_ROMX; }
+		| T_SECT_ROM0	{ $$ = SECTTYPE_ROM0; }
+		| T_SECT_HRAM	{ $$ = SECTTYPE_HRAM; }
+		| T_SECT_WRAMX	{ $$ = SECTTYPE_WRAMX; }
+		| T_SECT_SRAM	{ $$ = SECTTYPE_SRAM; }
+		| T_SECT_OAM	{ $$ = SECTTYPE_OAM; }
 		| T_SECT_HOME
 		{
 			warning("HOME section name is deprecated, use ROM0 instead.");
-			$$ = SECT_ROM0;
+			$$ = SECTTYPE_ROM0;
 		}
 		| T_SECT_DATA
 		{
 			warning("DATA section name is deprecated, use ROMX instead.");
-			$$ = SECT_ROMX;
+			$$ = SECTTYPE_ROMX;
 		}
 		| T_SECT_CODE
 		{
 			warning("CODE section name is deprecated, use ROMX instead.");
-			$$ = SECT_ROMX;
+			$$ = SECTTYPE_ROMX;
 		}
 		| T_SECT_BSS
 		{
 			warning("BSS section name is deprecated, use WRAM0 instead.");
-			$$ = SECT_WRAM0;
+			$$ = SECTTYPE_WRAM0;
 		}
 ;
 
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -96,21 +96,21 @@
 static uint32_t getmaxsectionsize(uint32_t secttype, char *sectname)
 {
 	switch (secttype) {
-	case SECT_ROM0:
+	case SECTTYPE_ROM0:
 		return 0x8000; /* If ROMX sections not used */
-	case SECT_ROMX:
+	case SECTTYPE_ROMX:
 		return 0x4000;
-	case SECT_VRAM:
+	case SECTTYPE_VRAM:
 		return 0x2000;
-	case SECT_SRAM:
+	case SECTTYPE_SRAM:
 		return 0x2000;
-	case SECT_WRAM0:
+	case SECTTYPE_WRAM0:
 		return 0x2000; /* If WRAMX sections not used */
-	case SECT_WRAMX:
+	case SECTTYPE_WRAMX:
 		return 0x1000;
-	case SECT_OAM:
+	case SECTTYPE_OAM:
 		return 0xA0;
-	case SECT_HRAM:
+	case SECTTYPE_HRAM:
 		return 0x7F;
 	default:
 		break;
@@ -241,7 +241,7 @@
 	fputlong(pSect->nBank, f);
 	fputlong(pSect->nAlign, f);
 
-	if ((pSect->nType == SECT_ROM0) || (pSect->nType == SECT_ROMX)) {
+	if (sect_HasData(pSect->nType)) {
 		struct Patch *pPatch;
 
 		fwrite(pSect->tData, 1, pSect->nPC, f);
@@ -265,23 +265,23 @@
 	int32_t sectid;
 
 	if (!(pSym->nType & SYMF_DEFINED)) {
-		type = SYM_IMPORT;
+		type = SYMTYPE_IMPORT;
 	} else if (pSym->nType & SYMF_EXPORT) {
-		type = SYM_EXPORT;
+		type = SYMTYPE_EXPORT;
 	} else {
-		type = SYM_LOCAL;
+		type = SYMTYPE_LOCAL;
 	}
 
 	switch (type) {
-	case SYM_LOCAL:
+	case SYMTYPE_LOCAL:
 		offset = pSym->nValue;
 		sectid = getsectid(pSym->pSection);
 		break;
-	case SYM_IMPORT:
+	case SYMTYPE_IMPORT:
 		offset = 0;
 		sectid = -1;
 		break;
-	case SYM_EXPORT:
+	case SYMTYPE_EXPORT:
 		offset = pSym->nValue;
 		if (pSym->nType & SYMF_CONST)
 			sectid = -1;
@@ -293,7 +293,7 @@
 	fputstring(pSym->tzName, f);
 	fputc(type, f);
 
-	if (type != SYM_IMPORT) {
+	if (type != SYMTYPE_IMPORT) {
 		fputstring(pSym->tzFileName, f);
 		fputlong(pSym->nFileLine, f);
 
@@ -496,8 +496,7 @@
 static void checkcodesection(void)
 {
 	checksection();
-	if (pCurrentSection->nType != SECT_ROM0 &&
-	    pCurrentSection->nType != SECT_ROMX) {
+	if (!sect_HasData(pCurrentSection->nType)) {
 		fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)",
 			   pCurrentSection->pzName);
 	} else if (nUnionDepth > 0) {
@@ -570,8 +569,7 @@
 	if (f == NULL)
 		fatalerror("Couldn't write file '%s'\n", tzObjectname);
 
-	fwrite(RGBDS_OBJECT_VERSION_STRING, 1,
-	       strlen(RGBDS_OBJECT_VERSION_STRING), f);
+	fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
 
 	fputlong(countsymbols(), f);
 	fputlong(countsections(), f);
@@ -647,7 +645,7 @@
 	pSect->charmap = NULL;
 
 	/* It is only needed to allocate memory for ROM sections. */
-	if (secttype == SECT_ROM0 || secttype == SECT_ROMX) {
+	if (sect_HasData(secttype)) {
 		uint32_t sectsize;
 
 		sectsize = getmaxsectionsize(secttype, pzName);
@@ -743,8 +741,7 @@
 {
 	checksection();
 	checksectionoverflow(skip);
-	if (!((pCurrentSection->nType == SECT_ROM0)
-		|| (pCurrentSection->nType == SECT_ROMX))) {
+	if (!sect_HasData(pCurrentSection->nType)) {
 		pCurrentSection->nPC += skip;
 		nPC += skip;
 		pPCSymbol->nValue += skip;
@@ -779,7 +776,7 @@
 	checksectionoverflow(1);
 	if (rpn_isReloc(expr)) {
 		pCurrentSection->tData[nPC] = 0;
-		createpatch(PATCH_BYTE, expr);
+		createpatch(PATCHTYPE_BYTE, expr);
 		pCurrentSection->nPC += 1;
 		nPC += 1;
 		pPCSymbol->nValue += 1;
@@ -815,7 +812,7 @@
 	if (rpn_isReloc(expr)) {
 		pCurrentSection->tData[nPC] = 0;
 		pCurrentSection->tData[nPC + 1] = 0;
-		createpatch(PATCH_WORD_L, expr);
+		createpatch(PATCHTYPE_WORD, expr);
 		pCurrentSection->nPC += 2;
 		nPC += 2;
 		pPCSymbol->nValue += 2;
@@ -854,7 +851,7 @@
 		pCurrentSection->tData[nPC + 1] = 0;
 		pCurrentSection->tData[nPC + 2] = 0;
 		pCurrentSection->tData[nPC + 3] = 0;
-		createpatch(PATCH_LONG_L, expr);
+		createpatch(PATCHTYPE_LONG, expr);
 		pCurrentSection->nPC += 4;
 		nPC += 4;
 		pPCSymbol->nValue += 4;
@@ -875,7 +872,7 @@
 
 	/* Always let the linker calculate the offset. */
 	pCurrentSection->tData[nPC] = 0;
-	createpatch(PATCH_BYTE_JR, expr);
+	createpatch(PATCHTYPE_JR, expr);
 	pCurrentSection->nPC += 1;
 	nPC += 1;
 	pPCSymbol->nValue += 1;
--- a/src/extern/err.c
+++ b/src/extern/err.c
@@ -14,13 +14,12 @@
 
 void rgbds_vwarn(const char *fmt, va_list ap)
 {
-	fprintf(stderr, "warning");
+	fprintf(stderr, "warning: ");
 	if (fmt) {
-		fputs(": ", stderr);
 		vfprintf(stderr, fmt, ap);
+		fputs(": ", stderr);
 	}
-	putc('\n', stderr);
-	perror(0);
+	perror(NULL);
 }
 
 void rgbds_vwarnx(const char *fmt, va_list ap)
@@ -35,12 +34,12 @@
 
 noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
 {
-	fprintf(stderr, "error");
+	fprintf(stderr, "error: ");
 	if (fmt) {
-		fputs(": ", stderr);
 		vfprintf(stderr, fmt, ap);
+		fputs(": ", stderr);
 	}
-	putc('\n', stderr);
+	perror(NULL);
 	exit(status);
 }
 
--- /dev/null
+++ b/src/hashmap.c
@@ -1,0 +1,118 @@
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "hashmap.h"
+#include "extern/err.h"
+
+/*
+ * The lower half of the hash is used to index the "master" table,
+ * the upper half is used to help resolve collisions more quickly
+ */
+#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t
+#define UINT_BITS(NB_BITS)  UINT_BITS_(NB_BITS)
+typedef UINT_BITS(HASH_NB_BITS) HashType;
+typedef UINT_BITS(HALF_HASH_NB_BITS) HalfHashType;
+
+struct HashMapEntry {
+	HalfHashType hash;
+	char const *key;
+	void *content;
+	struct HashMapEntry *next;
+};
+
+#define FNV_OFFSET_BASIS 0x811c9dc5
+#define FNV_PRIME 16777619
+
+/* FNV-1a hash */
+static HashType hash(char const *str)
+{
+	HashType hash = FNV_OFFSET_BASIS;
+
+	while (*str) {
+		hash ^= (uint8_t)*str++;
+		hash *= FNV_PRIME;
+	}
+	return hash;
+}
+
+bool hash_AddElement(HashMap map, char const *key, void *element)
+{
+	HashType hashedKey = hash(key);
+	HalfHashType index = hashedKey;
+	struct HashMapEntry *newEntry = malloc(sizeof(*newEntry));
+
+	if (!newEntry)
+		err(1, "%s: Failed to allocate new entry", __func__);
+
+	newEntry->hash = hashedKey >> HALF_HASH_NB_BITS;
+	newEntry->key = key;
+	newEntry->content = element;
+	newEntry->next = map[index];
+	map[index] = newEntry;
+
+	return newEntry->next != NULL;
+}
+
+bool hash_DeleteElement(HashMap map, char const *key)
+{
+	HashType hashedKey = hash(key);
+	struct HashMapEntry **ptr = &map[(HalfHashType)hashedKey];
+
+	while (*ptr) {
+		if (hashedKey >> HALF_HASH_NB_BITS == (*ptr)->hash
+		 && !strcmp((*ptr)->key, key)) {
+			struct HashMapEntry *next = (*ptr)->next;
+
+			free(*ptr);
+			*ptr = next;
+			return true;
+		}
+	}
+	return false;
+}
+
+void *hash_GetElement(HashMap const map, char const *key)
+{
+	HashType hashedKey = hash(key);
+	struct HashMapEntry *ptr = map[(HalfHashType)hashedKey];
+
+	while (ptr) {
+		if (hashedKey >> HALF_HASH_NB_BITS == ptr->hash
+		 && !strcmp(ptr->key, key)) {
+			return ptr->content;
+		}
+		ptr = ptr->next;
+	}
+	return NULL;
+}
+
+void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg)
+{
+	for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) {
+		struct HashMapEntry *ptr = map[i];
+
+		while (ptr) {
+			func(ptr->content, arg);
+			ptr = ptr->next;
+		}
+	}
+}
+
+void hash_EmptyMap(HashMap map)
+{
+	for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) {
+		struct HashMapEntry *ptr = map[i];
+
+		while (ptr) {
+			struct HashMapEntry *next = ptr->next;
+
+			free(ptr);
+			ptr = next;
+		}
+		map[i] = NULL;
+	}
+}
--- a/src/link/assign.c
+++ b/src/link/assign.c
@@ -1,723 +1,437 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <stdbool.h>
-#include <string.h>
+#include <stdlib.h>
 
-#include "extern/err.h"
-
 #include "link/assign.h"
-#include "link/mylink.h"
+#include "link/section.h"
+#include "link/symbol.h"
+#include "link/object.h"
 #include "link/main.h"
 #include "link/script.h"
-#include "link/symbol.h"
+#include "link/output.h"
 
-struct sFreeArea {
-	int32_t nOrg;
-	int32_t nSize;
-	struct sFreeArea *pPrev, *pNext;
-};
+#include "extern/err.h"
+#include "helpers.h"
 
-struct sSectionAttributes {
-	const char *name;
-	/* bank + offset = bank originally stored in a section struct */
-	int32_t bank;
-	int32_t offset;
-	int32_t minBank;
-	int32_t bankCount;
+struct MemoryLocation {
+	uint16_t address;
+	uint32_t bank;
 };
 
-struct sFreeArea *BankFree[BANK_INDEX_MAX];
-int32_t MaxAvail[BANK_INDEX_MAX];
-int32_t MaxBankUsed;
-int32_t MaxWBankUsed;
-int32_t MaxSBankUsed;
-int32_t MaxVBankUsed;
-
-const enum eSectionType SECT_MIN = SECT_WRAM0;
-const enum eSectionType SECT_MAX = SECT_OAM;
-const struct sSectionAttributes SECT_ATTRIBUTES[] = {
-	{"WRAM0", BANK_INDEX_WRAM0, 0, 0, BANK_COUNT_WRAM0},
-	{"VRAM",  BANK_INDEX_VRAM,  0, 0, BANK_COUNT_VRAM},
-	{"ROMX",  BANK_INDEX_ROMX, -BANK_MIN_ROMX, BANK_MIN_ROMX, BANK_COUNT_ROMX},
-	{"ROM0",  BANK_INDEX_ROM0,  0, 0, BANK_COUNT_ROM0},
-	{"HRAM",  BANK_INDEX_HRAM,  0, 0, BANK_COUNT_HRAM},
-	{"WRAMX", BANK_INDEX_WRAMX, -BANK_MIN_WRAMX, BANK_MIN_WRAMX, BANK_COUNT_WRAMX},
-	{"SRAM",  BANK_INDEX_SRAM,  0, 0, BANK_COUNT_SRAM},
-	{"OAM",   BANK_INDEX_OAM,   0, 0, BANK_COUNT_OAM}
+struct FreeSpace {
+	uint16_t address;
+	uint16_t size;
+	struct FreeSpace *next, *prev;
 };
 
-static void do_max_bank(enum eSectionType Type, int32_t nBank)
-{
-	switch (Type) {
-	case SECT_ROMX:
-		if (nBank > MaxBankUsed)
-			MaxBankUsed = nBank;
-		break;
-	case SECT_WRAMX:
-		if (nBank > MaxWBankUsed)
-			MaxWBankUsed = nBank;
-		break;
-	case SECT_SRAM:
-		if (nBank > MaxSBankUsed)
-			MaxSBankUsed = nBank;
-		break;
-	case SECT_VRAM:
-		if (nBank > MaxVBankUsed)
-			MaxVBankUsed = nBank;
-		break;
-	case SECT_ROM0:
-	case SECT_WRAM0:
-	case SECT_OAM:
-	case SECT_HRAM:
-	default:
-		break;
-	}
-}
+/* Table of free space for each bank */
+struct FreeSpace *memory[SECTTYPE_INVALID];
 
-void ensureSectionTypeIsValid(enum eSectionType type)
-{
-	if (type < SECT_MIN || type > SECT_MAX)
-		errx(1, "%s: Invalid section type found: %d", __func__, type);
-}
+uint64_t nbSectionsToAssign;
 
-int BankIndexIsROM0(int32_t bank)
+/**
+ * Init the free space-modelling structs
+ */
+static void initFreeSpace(void)
 {
-	return (bank >= BANK_INDEX_ROM0) &&
-	       (bank < (BANK_INDEX_ROM0 + BANK_COUNT_ROM0));
-}
+	for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
+		memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type));
+		if (!memory[type])
+			err(1, "Failed to init free space for region %d", type);
 
-int BankIndexIsROMX(int32_t bank)
-{
-	return (bank >= BANK_INDEX_ROMX) &&
-	       (bank < (BANK_INDEX_ROMX + BANK_COUNT_ROMX));
-}
-
-int BankIndexIsWRAM0(int32_t bank)
-{
-	return (bank >= BANK_INDEX_WRAM0) &&
-	       (bank < (BANK_INDEX_WRAM0 + BANK_COUNT_WRAM0));
-}
-
-int BankIndexIsWRAMX(int32_t bank)
-{
-	return (bank >= BANK_INDEX_WRAMX) &&
-	       (bank < (BANK_INDEX_WRAMX + BANK_COUNT_WRAMX));
-}
-
-int BankIndexIsVRAM(int32_t bank)
-{
-	return (bank >= BANK_INDEX_VRAM) &&
-	       (bank < (BANK_INDEX_VRAM + BANK_COUNT_VRAM));
-}
-
-int BankIndexIsOAM(int32_t bank)
-{
-	return (bank >= BANK_INDEX_OAM) &&
-	       (bank < (BANK_INDEX_OAM + BANK_COUNT_OAM));
-}
-
-int BankIndexIsHRAM(int32_t bank)
-{
-	return (bank >= BANK_INDEX_HRAM) &&
-	       (bank < (BANK_INDEX_HRAM + BANK_COUNT_HRAM));
-}
-
-int BankIndexIsSRAM(int32_t bank)
-{
-	return (bank >= BANK_INDEX_SRAM) &&
-	       (bank < (BANK_INDEX_SRAM + BANK_COUNT_SRAM));
-}
-
-int32_t area_Avail(int32_t bank)
-{
-	int32_t r;
-	struct sFreeArea *pArea;
-
-	r = 0;
-	pArea = BankFree[bank];
-
-	while (pArea) {
-		r += pArea->nSize;
-		pArea = pArea->pNext;
+		for (uint32_t bank = 0; bank < nbbanks(type); bank++) {
+			memory[type][bank].next =
+				malloc(sizeof(*memory[type][0].next));
+			if (!memory[type][bank].next)
+				err(1, "Failed to init free space for region %d bank %u",
+				    type, bank);
+			memory[type][bank].next->address = startaddr[type];
+			memory[type][bank].next->size    = maxsize[type];
+			memory[type][bank].next->next    = NULL;
+			memory[type][bank].next->prev    = &memory[type][bank];
+		}
 	}
-
-	return r;
 }
 
-int32_t area_doAlloc(struct sFreeArea *pArea, int32_t org, int32_t size)
+/**
+ * Alter sections' attributes based on the linker script
+ */
+static void processLinkerScript(void)
 {
-	if (size == 0) {
-		/* 0-byte SECTIONs don't take any room, they can go anywhere */
-		return org;
-	}
+	if (!linkerScriptName)
+		return;
+	verbosePrint("Reading linker script...\n");
 
-	if ((org >= pArea->nOrg)
-	    && ((org + size) <= (pArea->nOrg + pArea->nSize))) {
+	linkerScript = openFile(linkerScriptName, "r");
 
-		if (org == pArea->nOrg) {
-			pArea->nOrg += size;
-			pArea->nSize -= size;
-			return org;
-		}
+	/* Modify all sections according to the linker script */
+	struct SectionPlacement *placement;
 
-		if ((org + size) == (pArea->nOrg + pArea->nSize)) {
-			pArea->nSize -= size;
-			return org;
-		}
+	while ((placement = script_NextSection())) {
+		struct Section *section = placement->section;
 
-		struct sFreeArea *pNewArea;
+		/* Check if this doesn't conflict with what the code says */
+		if (section->isBankFixed && placement->bank != section->bank)
+			errx(1, "Linker script contradicts \"%s\"'s bank placement",
+			     section->name);
+		if (section->isAddressFixed && placement->org != section->org)
+			errx(1, "Linker script contradicts \"%s\"'s address placement",
+			     section->name);
+		if (section->isAlignFixed
+		 && (placement->org & section->alignMask) != 0)
+			errx(1, "Linker script contradicts \"%s\"'s alignment",
+			     section->name);
 
-		pNewArea = malloc(sizeof(struct sFreeArea));
-
-		if (pNewArea == NULL)
-			err(1, "%s: Failed to allocate memory", __func__);
-
-		*pNewArea = *pArea;
-		pNewArea->pPrev = pArea;
-		pArea->pNext = pNewArea;
-		pArea->nSize = org - pArea->nOrg;
-		pNewArea->nOrg = org + size;
-		pNewArea->nSize -= size + pArea->nSize;
-
-		return org;
+		section->isAddressFixed = true;
+		section->org = placement->org;
+		section->isBankFixed = true;
+		section->bank = placement->bank;
+		section->isAlignFixed = false; /* The alignment is satisfied */
 	}
 
-	return -1;
+	fclose(linkerScript);
 }
 
-int32_t area_AllocAbs(struct sFreeArea **ppArea, int32_t org, int32_t size)
+/**
+ * Assigns a section to a given memory location
+ * @param section The section to assign
+ * @param location The location to assign the section to
+ */
+static inline void assignSection(struct Section *section,
+				 struct MemoryLocation const *location)
 {
-	struct sFreeArea *pArea;
+	section->org = location->address;
+	section->bank = location->bank;
 
-	pArea = *ppArea;
-	while (pArea) {
-		int32_t result = area_doAlloc(pArea, org, size);
+	nbSectionsToAssign--;
 
-		if (result != -1)
-			return result;
-
-		ppArea = &(pArea->pNext);
-		pArea = *ppArea;
-	}
-
-	return -1;
+	out_AddSection(section);
 }
 
-int32_t area_AllocAbsAnyBank(int32_t org, int32_t size, enum eSectionType type)
+/**
+ * Checks whether a given location is suitable for placing a given section
+ * This checks not only that the location has enough room for the section, but
+ * also that the constraints (alignment...) are respected.
+ * @param section The section to be placed
+ * @param freeSpace The candidate free space to place the section into
+ * @param location The location to attempt placing the section at
+ * @return True if the location is suitable, false otherwise.
+ */
+static bool isLocationSuitable(struct Section const *section,
+			       struct FreeSpace const *freeSpace,
+			       struct MemoryLocation const *location)
 {
-	ensureSectionTypeIsValid(type);
+	if (section->isAddressFixed && section->org != location->address)
+		return false;
 
-	int32_t startBank = SECT_ATTRIBUTES[type].bank;
-	int32_t bankCount = SECT_ATTRIBUTES[type].bankCount;
+	if (section->isAlignFixed && location->address & section->alignMask)
+		return false;
 
-	for (int32_t i = 0; i < bankCount; i++) {
-		if (area_AllocAbs(&BankFree[startBank + i], org, size) != -1)
-			return startBank + i;
-	}
-
-	return -1;
+	return location->address + section->size
+					<= freeSpace->address + freeSpace->size;
 }
 
-int32_t area_Alloc(struct sFreeArea **ppArea, int32_t size, int32_t alignment)
+/**
+ * Finds a suitable location to place a section at.
+ * @param section The section to be placed
+ * @param location A pointer to a location struct that will be filled
+ * @return A pointer to the free space encompassing the location, or NULL if
+ *         none was found
+ */
+static struct FreeSpace *getPlacement(struct Section const *section,
+				      struct MemoryLocation *location)
 {
-	struct sFreeArea *pArea;
+	location->bank = section->isBankFixed
+				? section->bank
+				: bankranges[section->type][0];
+	struct FreeSpace *space;
 
-	if (alignment < 1)
-		alignment = 1;
+	for (;;) {
+		/* Switch to the beginning of the next bank */
+#define BANK_INDEX (location->bank - bankranges[section->type][0])
+		space = memory[section->type][BANK_INDEX].next;
+		if (space)
+			location->address = space->address;
 
-	pArea = *ppArea;
-	while (pArea) {
-		int32_t org = pArea->nOrg;
+		/* Process locations in that bank */
+		while (space) {
+			/* If that location is OK, return it */
+			if (isLocationSuitable(section, space, location))
+				return space;
 
-		if (org % alignment)
-			org += alignment;
+			/* Go to the next *possible* location */
+			if (section->isAddressFixed) {
+				/*
+				 * If the address is fixed, there can be only
+				 * one candidate block per bank; if we already
+				 * reached it, give up.
+				 */
+				if (location->address < section->org)
+					location->address = section->org;
+				else
+					/* Try again in next bank */
+					space = NULL;
+			} else if (section->isAlignFixed) {
+				/* Move to next aligned location */
+				location->address &= ~section->alignMask;
+				location->address += section->alignMask + 1;
+			} else {
+				/* Any location is fine, so, next free block */
+				space = space->next;
+				if (space)
+					location->address = space->address;
+			}
 
-		org -= org % alignment;
+			/*
+			 * If that location is past the current block's end,
+			 * go forwards until that is no longer the case.
+			 */
+			while (space && location->address >=
+						space->address + space->size)
+				space = space->next;
 
-		int32_t result = area_doAlloc(pArea, org, size);
-
-		if (result != -1)
-			return result;
-
-		ppArea = &(pArea->pNext);
-		pArea = *ppArea;
-	}
-
-	return -1;
-}
-
-int32_t area_AllocAnyBank(int32_t size, int32_t alignment,
-			  enum eSectionType type)
-{
-	ensureSectionTypeIsValid(type);
-
-	int32_t i, org;
-	int32_t startBank = SECT_ATTRIBUTES[type].bank;
-	int32_t bankCount = SECT_ATTRIBUTES[type].bankCount;
-
-	for (i = 0; i < bankCount; i++) {
-		org = area_Alloc(&BankFree[startBank + i], size, alignment);
-		if (org != -1)
-			return ((startBank + i) << 16) | org;
-	}
-
-	return -1;
-}
-
-struct sSection *FindLargestSection(enum eSectionType type, bool bankFixed)
-{
-	struct sSection *pSection, *r = NULL;
-	int32_t nLargest = 0;
-	int32_t nLargestAlignment = 0;
-
-	pSection = pSections;
-	while (pSection) {
-		if (pSection->oAssigned == 0 && pSection->Type == type
-		    && (bankFixed ^ (pSection->nBank == -1))) {
-			if (pSection->nAlign > nLargestAlignment
-			    || (pSection->nAlign == nLargestAlignment
-				&& pSection->nByteSize > nLargest)) {
-
-				nLargest = pSection->nByteSize;
-				nLargestAlignment = pSection->nAlign;
-				r = pSection;
-			}
+			/* Try again with the new location/free space combo */
 		}
-		pSection = pSection->pNext;
-	}
 
-	return r;
-}
+		if (section->isBankFixed)
+			return NULL;
 
-int32_t IsSectionNameInUse(const char *name)
-{
-	const struct sSection *pSection = pSections;
-
-	while (pSection) {
-		if (strcmp(pSection->pzName, name) == 0)
-			return 1;
-
-		pSection = pSection->pNext;
+		/* Try again in the next bank */
+		location->bank++;
+		if (location->bank > bankranges[section->type][1])
+			return NULL;
+#undef BANK_INDEX
 	}
-
-	return 0;
 }
 
-struct sSection *GetSectionByName(const char *name)
+/**
+ * Places a section in a suitable location, or error out if it fails to.
+ * @warning Due to the implemented algorithm, this should be called with
+ *          sections of decreasing size.
+ * @param section The section to place
+ */
+static void placeSection(struct Section *section)
 {
-	struct sSection *pSection = pSections;
+	struct MemoryLocation location;
 
-	while (pSection) {
-		if (strcmp(pSection->pzName, name) == 0)
-			return pSection;
-
-		pSection = pSection->pNext;
-	}
-
-	return NULL;
-}
-
-int32_t IsSectionSameTypeBankAndAttrs(const char *name,
-				      enum eSectionType type, int32_t bank,
-				      int32_t org, int32_t align)
-{
-	const struct sSection *pSection;
-
-	for (pSection = pSections; pSection; pSection = pSection->pNext) {
-		/* Skip if it has already been assigned */
-		if (pSection->oAssigned == 1)
-			continue;
-
-		/* Check if it has the same name */
-		if (strcmp(pSection->pzName, name) != 0)
-			continue;
-
+	/* Specially handle 0-byte SECTIONs, as they can't overlap anything */
+	if (section->size == 0) {
 		/*
-		 * The section has the same name, now check if there is a
-		 * mismatch or not.
+		 * Unless the SECTION's address was fixed, the starting address
+		 * is fine for any alignment, as checked in sect_DoSanityChecks.
 		 */
-
-		/* Section must have the same attributes or float */
-		if ((pSection->nOrg != -1 && pSection->nOrg != org) ||
-		    (pSection->nAlign != 1 && pSection->nAlign != align))
-			return 0;
-
-		/* It must have the same type in source and linkerscript */
-		if (pSection->Type != type)
-			return 0;
-
-		/* Bank number must be unassigned in source or equal */
-		if (pSection->nBank != -1 && pSection->nBank != bank)
-			return 0;
-
-		return 1;
+		location.address = section->isAddressFixed
+						? section->org
+						: startaddr[section->type];
+		location.bank = section->isBankFixed
+						? section->bank
+						: bankranges[section->type][0];
+		assignSection(section, &location);
+		return;
 	}
 
-	errx(1, "Section \"%s\" not found (or already used).\n", name);
-}
+	/*
+	 * Place section using first-fit decreasing algorithm
+	 * https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
+	 */
+	struct FreeSpace *freeSpace = getPlacement(section, &location);
 
-uint32_t AssignSectionAddressAndBankByName(const char *name, uint32_t address,
-					   int32_t bank)
-{
-	struct sSection *pSection;
+	if (freeSpace) {
+		assignSection(section, &location);
 
-	for (pSection = pSections; pSection; pSection = pSection->pNext) {
-		/* Skip if it has already been assigned */
-		if (pSection->oAssigned == 1)
-			continue;
+		/* Split the free space */
+		bool noLeftSpace  = freeSpace->address == section->org;
+		bool noRightSpace = freeSpace->address + freeSpace->size
+					== section->org + section->size;
+		if (noLeftSpace && noRightSpace) {
+			/* The free space is entirely deleted */
+			freeSpace->prev->next = freeSpace->next;
+			if (freeSpace->next)
+				freeSpace->next->prev = freeSpace->prev;
+			/*
+			 * If the space is the last one on the list, set its
+			 * size to 0 so it doesn't get picked, but don't free()
+			 * it as it will be freed when cleaning up
+			 */
+			free(freeSpace);
+		} else if (!noLeftSpace && !noRightSpace) {
+			/* The free space is split in two */
+			struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
 
-		/* Check if it has the same name */
-		if (strcmp(pSection->pzName, name) != 0)
-			continue;
-
-		/* Section has been found. */
-
-		/* The bank can be left as unassigned or be the same */
-		if (pSection->nBank != -1 && pSection->nBank != bank) {
-			errx(1, "Section \"%s\" from linkerscript has different bank number than in the source.\n",
-			     name);
+			if (!newSpace)
+				err(1, "Failed to split new free space");
+			/* Append the new space after the chosen one */
+			newSpace->prev = freeSpace;
+			newSpace->next = freeSpace->next;
+			if (freeSpace->next)
+				freeSpace->next->prev = newSpace;
+			freeSpace->next = newSpace;
+			/* Set its parameters */
+			newSpace->address = section->org + section->size;
+			newSpace->size = freeSpace->address + freeSpace->size -
+				newSpace->address;
+			/* Set the original space's new parameters */
+			freeSpace->size = section->org - freeSpace->address;
+			/* address is unmodified */
+		} else if (noLeftSpace) {
+			/* The free space is only moved and resized */
+			freeSpace->address += section->size;
+			freeSpace->size -= section->size;
+		} else {
+			/* The free space is only resized */
+			freeSpace->size -= section->size;
 		}
-
-		pSection->nOrg = address;
-		pSection->nBank = bank;
-		pSection->nAlign = -1;
-
-		return pSection->nByteSize;
+		return;
 	}
 
-	errx(1, "Section \"%s\" not found (or already used).\n", name);
-}
-
-bool VerifyAndSetBank(struct sSection *pSection)
-{
-	enum eSectionType Type = pSection->Type;
-
-	ensureSectionTypeIsValid(Type);
-
-	if (pSection->nBank >= SECT_ATTRIBUTES[Type].minBank) {
-		if (pSection->nBank < SECT_ATTRIBUTES[Type].minBank
-				    + SECT_ATTRIBUTES[Type].bankCount) {
-			pSection->nBank += SECT_ATTRIBUTES[Type].bank
-					 + SECT_ATTRIBUTES[Type].offset;
-			return true;
-		}
+	if (section->isBankFixed && nbbanks(section->type) != 1) {
+		if (section->isAddressFixed)
+			errx(1, "Unable to place \"%s\" (%s section) at $%02x:%04x",
+			     section->name, typeNames[section->type],
+			     section->bank, section->org);
+		else if (section->isAlignFixed)
+			errx(1, "Unable to place \"%s\" (%s section) in bank %u with align mask %x",
+			     section->name, typeNames[section->type],
+			     section->bank, ~section->alignMask);
+		else
+			errx(1, "Unable to place \"%s\" (%s section) in bank %u",
+			     section->name, typeNames[section->type],
+			     section->bank);
+	} else {
+		if (section->isAddressFixed)
+			errx(1, "Unable to place \"%s\" (%s section) at address $%x",
+			     section->name, typeNames[section->type],
+			     section->org);
+		else if (section->isAlignFixed)
+			errx(1, "Unable to place \"%s\" (%s section) with align mask %x",
+			     section->name, typeNames[section->type],
+			     ~section->alignMask);
+		else
+			errx(1, "Unable to place \"%s\" (%s section) anywhere",
+			     section->name, typeNames[section->type]);
 	}
-
-	return false;
 }
 
-void AssignFixedBankSections(enum eSectionType type)
-{
-	ensureSectionTypeIsValid(type);
+struct UnassignedSection {
+	struct Section *section;
+	struct UnassignedSection *next;
+};
 
-	struct sSection *pSection;
+#define  BANK_CONSTRAINED (1 << 2)
+#define   ORG_CONSTRAINED (1 << 1)
+#define ALIGN_CONSTRAINED (1 << 0)
+static struct UnassignedSection *unassignedSections[1 << 3] = {0};
+static struct UnassignedSection *sections;
 
-	while ((pSection = FindLargestSection(type, true))) {
-		if (VerifyAndSetBank(pSection)) {
-			pSection->nOrg = area_Alloc(&BankFree[pSection->nBank],
-						    pSection->nByteSize,
-						    pSection->nAlign);
-			if (pSection->nOrg != -1) {
-				pSection->oAssigned = 1;
-				do_max_bank(pSection->Type, pSection->nBank);
-				continue;
-			}
-		}
-
-		if (pSection->nAlign <= 1) {
-			errx(1, "Unable to place '%s' (%s section) in bank $%02lX",
-			     pSection->pzName,
-			     SECT_ATTRIBUTES[pSection->Type].name,
-			     pSection->nBank);
-		} else {
-			errx(1, "Unable to place '%s' (%s section) in bank $%02lX (with $%lX-byte alignment)",
-			     pSection->pzName,
-			     SECT_ATTRIBUTES[pSection->Type].name,
-			     pSection->nBank, pSection->nAlign);
-		}
-	}
-}
-
-void AssignFloatingBankSections(enum eSectionType type)
+/**
+ * Categorize a section depending on how constrained it is
+ * This is so the most-constrained sections are placed first
+ * @param section The section to categorize
+ * @param arg Callback arg, unused
+ */
+static void categorizeSection(struct Section *section, void *arg)
 {
-	ensureSectionTypeIsValid(type);
+	(void)arg;
+	uint8_t constraints = 0;
 
-	struct sSection *pSection;
+	if (section->isBankFixed)
+		constraints |= BANK_CONSTRAINED;
+	if (section->isAddressFixed)
+		constraints |= ORG_CONSTRAINED;
+	/* Can't have both! */
+	else if (section->isAlignFixed)
+		constraints |= ALIGN_CONSTRAINED;
 
-	while ((pSection = FindLargestSection(type, false))) {
-		int32_t org;
+	struct UnassignedSection **ptr = &unassignedSections[constraints];
 
-		org = area_AllocAnyBank(pSection->nByteSize, pSection->nAlign,
-					type);
+	/* Insert section while keeping the list sorted by decreasing size */
+	while (*ptr && (*ptr)->section->size > section->size)
+		ptr = &(*ptr)->next;
 
-		if (org != -1) {
-			if (options & OPT_OVERLAY)
-				errx(1, "All sections must be fixed when using an overlay file.");
+	sections[nbSectionsToAssign].section = section;
+	sections[nbSectionsToAssign].next = *ptr;
+	*ptr = &sections[nbSectionsToAssign];
 
-			pSection->nOrg = org & 0xFFFF;
-			pSection->nBank = org >> 16;
-			pSection->oAssigned = 1;
-			do_max_bank(pSection->Type, pSection->nBank);
-		} else {
-			const char *locality = "anywhere";
-
-			if (SECT_ATTRIBUTES[pSection->Type].bankCount > 1)
-				locality = "in any bank";
-
-			if (pSection->nAlign <= 1) {
-				errx(1, "Unable to place '%s' (%s section) %s",
-				     pSection->pzName,
-				     SECT_ATTRIBUTES[type].name, locality);
-			} else {
-				errx(1, "Unable to place '%s' (%s section) %s (with $%lX-byte alignment)",
-				     pSection->pzName,
-				     SECT_ATTRIBUTES[type].name, locality,
-				     pSection->nAlign);
-			}
-		}
-	}
+	nbSectionsToAssign++;
 }
 
-char *tzLinkerscriptName;
-
-void SetLinkerscriptName(char *tzLinkerscriptFile)
+void assign_AssignSections(void)
 {
-	tzLinkerscriptName = tzLinkerscriptFile;
-}
+	verbosePrint("Beginning assignment...\n");
 
-void AssignSections(void)
-{
-	struct sSection *pSection;
+	/** Initialize assignment **/
 
-	MaxBankUsed = 0;
+	/* Generate linked lists of sections to assign */
+	sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
+	if (!sections)
+		err(1, "Failed to allocate memory for section assignment");
 
-	/*
-	 * Initialize the memory areas
-	 */
+	initFreeSpace();
 
-	for (int32_t i = 0; i < BANK_INDEX_MAX; i += 1) {
-		BankFree[i] = malloc(sizeof(*BankFree[i]));
+	/* Process linker script, if any */
+	processLinkerScript();
 
-		if (!BankFree[i]) {
-			errx(1, "%s: Couldn't allocate mem for bank %d",
-			     __func__, i);
-		}
+	nbSectionsToAssign = 0;
+	sect_ForEach(categorizeSection, NULL);
 
-		if (BankIndexIsROM0(i)) {
-			/* ROM0 bank */
-			BankFree[i]->nOrg = 0x0000;
-			if (options & OPT_TINY)
-				BankFree[i]->nSize = 0x8000;
-			else
-				BankFree[i]->nSize = 0x4000;
-		} else if (BankIndexIsROMX(i)) {
-			/* Swappable ROM bank */
-			BankFree[i]->nOrg = 0x4000;
-			BankFree[i]->nSize = 0x4000;
-		} else if (BankIndexIsWRAM0(i)) {
-			/* WRAM */
-			BankFree[i]->nOrg = 0xC000;
-			if (options & OPT_CONTWRAM)
-				BankFree[i]->nSize = 0x2000;
-			else
-				BankFree[i]->nSize = 0x1000;
-		} else if (BankIndexIsSRAM(i)) {
-			/* Swappable SRAM bank */
-			BankFree[i]->nOrg = 0xA000;
-			BankFree[i]->nSize = 0x2000;
-		} else if (BankIndexIsWRAMX(i)) {
-			/* Swappable WRAM bank */
-			BankFree[i]->nOrg = 0xD000;
-			BankFree[i]->nSize = 0x1000;
-		} else if (BankIndexIsVRAM(i)) {
-			/* Swappable VRAM bank */
-			BankFree[i]->nOrg = 0x8000;
-			if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM)
-				BankFree[i]->nSize = 0;
-			else
-				BankFree[i]->nSize = 0x2000;
-		} else if (BankIndexIsOAM(i)) {
-			BankFree[i]->nOrg = 0xFE00;
-			BankFree[i]->nSize = 0x00A0;
-		} else if (BankIndexIsHRAM(i)) {
-			/* HRAM */
-			BankFree[i]->nOrg = 0xFF80;
-			BankFree[i]->nSize = 0x007F;
-		} else {
-			errx(1, "%s: Unknown bank type %d", __func__, i);
-		}
+	/** Place sections, starting with the most constrained **/
 
-		MaxAvail[i] = BankFree[i]->nSize;
-		BankFree[i]->pPrev = NULL;
-		BankFree[i]->pNext = NULL;
-	}
+	/* Specially process fully-constrained sections because of overlaying */
+	struct UnassignedSection *sectionPtr =
+		unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED];
 
-	/*
-	 * First, let's parse the linkerscript.
-	 */
-
-	if (tzLinkerscriptName) {
-		script_InitSections();
-		script_Parse(tzLinkerscriptName);
+	verbosePrint("Assigning bank+org-constrained...\n");
+	while (sectionPtr) {
+		placeSection(sectionPtr->section);
+		sectionPtr = sectionPtr->next;
 	}
 
-	/*
-	 * Second, let's assign all the fixed sections...
-	 */
+	/* If all sections were fully constrained, we have nothing left to do */
+	if (!nbSectionsToAssign)
+		return;
 
-	for (pSection = pSections ; pSection; pSection = pSection->pNext) {
-		if (!((pSection->nOrg != -1 || pSection->nBank != -1)
-		    && pSection->oAssigned == 0))
-			continue;
+	/* Overlaying requires only fully-constrained sections */
+	verbosePrint("Assigning other sections...\n");
+	if (overlayFileName)
+		errx(1, "All sections must be fixed when using an overlay file; %u, %sn't",
+		     nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
 
-		/* User wants to have a say... */
+	/* Assign all remaining sections by decreasing constraint order */
+	for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
+	     constraints >= 0; constraints--) {
+		sectionPtr = unassignedSections[constraints];
 
-		switch (pSection->Type) {
-		case SECT_WRAM0:
-		case SECT_HRAM:
-		case SECT_ROM0:
-		case SECT_OAM:
-			pSection->nBank = SECT_ATTRIBUTES[pSection->Type].bank;
-			if (area_AllocAbs(&BankFree[pSection->nBank],
-					  pSection->nOrg,
-					  pSection->nByteSize) == -1) {
-				errx(1, "Unable to place '%s' (%s section) at $%X",
-				     pSection->pzName,
-				     SECT_ATTRIBUTES[pSection->Type].name,
-				     pSection->nOrg);
-			}
-			pSection->oAssigned = 1;
-			break;
-
-		case SECT_SRAM:
-		case SECT_WRAMX:
-		case SECT_VRAM:
-		case SECT_ROMX:
-			if (!(pSection->nBank != -1 && pSection->nOrg != -1))
-				break;
-
-			if (VerifyAndSetBank(pSection) &&
-			    area_AllocAbs(&BankFree[pSection->nBank],
-					  pSection->nOrg,
-					  pSection->nByteSize) != -1) {
-				do_max_bank(pSection->Type, pSection->nBank);
-				pSection->oAssigned = 1;
-			} else {
-				errx(1, "Unable to place '%s' (%s section) at $%X in bank $%02X",
-				     pSection->pzName,
-				     SECT_ATTRIBUTES[pSection->Type].name,
-				     pSection->nOrg, pSection->nBank);
-			}
-			break;
-		default:
-			errx(1, "%s: Internal error: Type %d", __func__,
-			     pSection->Type);
+		while (sectionPtr) {
+			placeSection(sectionPtr->section);
+			sectionPtr = sectionPtr->next;
 		}
-	}
 
-	/*
-	 * Next, let's assign all the bankfixed ONLY sections...
-	 */
-	for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++)
-		AssignFixedBankSections(i);
-
-	/*
-	 * Now, let's assign all the floating bank but fixed ROMX sections...
-	 */
-
-	for (pSection = pSections ; pSection; pSection = pSection->pNext) {
-		if (!(pSection->oAssigned == 0
-		    && pSection->nOrg != -1 && pSection->nBank == -1))
-			continue;
-
-		if (options & OPT_OVERLAY) {
-			errx(1, "All sections must be fixed when using an overlay file: '%s'",
-			     pSection->pzName);
-		}
-
-		switch (pSection->Type) {
-		case SECT_ROMX:
-		case SECT_VRAM:
-		case SECT_SRAM:
-		case SECT_WRAMX:
-			pSection->nBank =
-				area_AllocAbsAnyBank(pSection->nOrg,
-						     pSection->nByteSize,
-						     pSection->Type);
-
-			if (pSection->nBank == -1) {
-				errx(1, "Unable to place '%s' (%s section) at $%X in any bank",
-				     pSection->pzName,
-				     SECT_ATTRIBUTES[pSection->Type].name,
-				     pSection->nOrg);
-			}
-			pSection->oAssigned = 1;
-			do_max_bank(pSection->Type, pSection->nBank);
-			break;
-
-		case SECT_ROM0:
-		case SECT_WRAM0:
-		case SECT_OAM:
-		case SECT_HRAM:
-		default: /* Handle other sections later */
-			break;
-		}
+		if (!nbSectionsToAssign)
+			return;
 	}
 
-	/*
-	 * OK, all that nasty stuff is done so let's assign all the other
-	 * sections
-	 */
-	for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++)
-		AssignFloatingBankSections(i);
+	trap_;
 }
 
-void CreateSymbolTable(void)
+void assign_Cleanup(void)
 {
-	const struct sSection *pSect;
+	for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
+		for (uint32_t bank = 0; bank < nbbanks(type); bank++) {
+			struct FreeSpace *ptr =
+				memory[type][bank].next;
 
-	sym_Init();
+			while (ptr) {
+				struct FreeSpace *next = ptr->next;
 
-	pSect = pSections;
-
-	while (pSect) {
-		int32_t i;
-
-		i = pSect->nNumberOfSymbols;
-
-		while (i--) {
-			const struct sSymbol *tSymbol = pSect->tSymbols[i];
-
-			if ((tSymbol->Type == SYM_EXPORT) &&
-			    ((tSymbol->pSection == pSect) ||
-				(tSymbol->pSection == NULL))) {
-				if (tSymbol->pSection == NULL) {
-					sym_CreateSymbol(tSymbol->pzName,
-							 tSymbol->nOffset,
-							 -1,
-							 tSymbol->pzObjFileName,
-							 tSymbol->pzFileName,
-							 tSymbol->nFileLine);
-				} else {
-					sym_CreateSymbol(tSymbol->pzName,
-							 pSect->nOrg +
-							   tSymbol->nOffset,
-							 pSect->nBank,
-							 tSymbol->pzObjFileName,
-							 tSymbol->pzFileName,
-							 tSymbol->nFileLine);
-				}
+				free(ptr);
+				ptr = next;
 			}
 		}
-		pSect = pSect->pNext;
+
+		free(memory[type]);
 	}
+
+	free(sections);
+
+	script_Cleanup();
 }
--- a/src/link/lexer.l
+++ /dev/null
@@ -1,194 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-%option noinput
-%option yylineno
-
-%{
-#include <stdarg.h>
-#include <stdint.h>
-#include <unistd.h>
-
-#include "extern/err.h"
-
-#include "link/mylink.h"
-#include "link/script.h"
-
-#include "parser.h"
-
-#include "types.h"
-
-extern int yyparse(void);
-
-/* File include stack. */
-
-#define MAX_INCLUDE_DEPTH 8
-
-static int32_t include_stack_ptr;
-
-static YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
-static char include_path[MAX_INCLUDE_DEPTH][_MAX_PATH + 1];
-static int32_t include_line[MAX_INCLUDE_DEPTH];
-
-static char linkerscript_path[_MAX_PATH + 1]; /* Base file */
-%}
-
-%%
-
-\"([^\\\"]|\\.)*\" {
-			if (strlen(yytext) > sizeof(yylval.s) - 1) {
-				script_fatalerror("String is too long: %s\n.",
-						  yytext);
-			}
-
-			if (strlen(yytext) < 3) { /* 2 quotes + 1 character */
-				script_fatalerror("String %s is invalid\n.",
-						  yytext);
-			}
-
-			/* Ignore first quote */
-			yytext++;
-			strcpy(yylval.s, yytext);
-			/* Remove end quote */
-			yylval.s[strlen(yylval.s)-1] = '\0';
-
-			return STRING;
-		}
-
-\$[a-fA-F0-9]+	{
-			yytext++; /* Skip prefix */
-			yylval.i = strtol(yytext, NULL, 16);
-			return INTEGER;
-		}
-[0-9]+		{
-			yylval.i = strtol(yytext, NULL, 10);
-			return INTEGER;
-		}
-
-(?i:ROM0)	{ strcpy(yylval.s, "ROM0");  return SECTION_NONBANKED; }
-(?i:ROMX)	{ strcpy(yylval.s, "ROMX");  return SECTION_BANKED; }
-(?i:VRAM)	{ strcpy(yylval.s, "VRAM");  return SECTION_BANKED; }
-(?i:WRAM0)	{ strcpy(yylval.s, "WRAM0"); return SECTION_NONBANKED; }
-(?i:WRAMX)	{ strcpy(yylval.s, "WRAMX"); return SECTION_BANKED; }
-(?i:SRAM)	{ strcpy(yylval.s, "SRAM");  return SECTION_BANKED; }
-(?i:OAM)	{ strcpy(yylval.s, "OAM");   return SECTION_NONBANKED; }
-(?i:HRAM)	{ strcpy(yylval.s, "HRAM");  return SECTION_NONBANKED; }
-
-(?i:ALIGN)	{ return COMMAND_ALIGN; }
-(?i:ORG)	{ return COMMAND_ORG; }
-
-(?i:INCLUDE)	{ return COMMAND_INCLUDE; }
-
-"\n"		{ return NEWLINE; }
-
-;.*		{ /* Ignore comments. A dot doesn't match newline. */ }
-
-[[:space:]]	{ /* Ignore whitespace. */ }
-
-.		{ script_fatalerror("Invalid character [%s]\n.", yytext); }
-
-%%
-
-extern FILE *yyin;
-
-void script_Parse(const char * path)
-{
-	yyin = fopen(path, "r");
-
-	if (!yyin)
-		errx(1, "Error opening file! \"%s\"\n", path);
-
-	strncpy(linkerscript_path, path, sizeof(linkerscript_path));
-	linkerscript_path[sizeof(linkerscript_path) - 1] = '\0';
-
-	do {
-		yyparse();
-	} while (!feof(yyin));
-
-	fclose(yyin);
-}
-
-void script_IncludeFile(const char * path)
-{
-	if (include_stack_ptr == (MAX_INCLUDE_DEPTH - 1))
-		script_fatalerror("Includes nested too deeply.");
-
-	include_line[include_stack_ptr] = yylineno;
-	include_stack[include_stack_ptr] = YY_CURRENT_BUFFER;
-
-	include_stack_ptr++;
-
-	yyin = fopen(path, "r");
-
-	if (!yyin)
-		script_fatalerror("Couldn't open file \"%s\"", path);
-
-	strncpy(include_path[include_stack_ptr], path, sizeof(include_path[0]));
-	include_path[include_stack_ptr][sizeof(include_path[0]) - 1] = '\0';
-
-	yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
-	yylineno = 1;
-
-	/*
-	 * The INCLUDE keyword is handled before reaching a newline token, it's
-	 * handled right after parsing the string with the file name that has to
-	 * be included. It isn't actually needed to include a newline after the
-	 * path, the last line of the linkerscript doesn't need to have a
-	 * newline character but it can have a command.
-	 *
-	 * This means that, when opening a new file, we must tell the parser
-	 * that what it is going to start at a new line or it will think that
-	 * the first line of the included script is the continuation of the
-	 * INCLUDE line of the parent script. If this is not done, the first
-	 * line of an included linkerscript can only be a comment (or empty).
-	 */
-	unput('\n');
-}
-
-int32_t script_IncludeDepthGet(void)
-{
-	return include_stack_ptr;
-}
-
-void script_IncludePop(void)
-{
-	fclose(yyin);
-
-	include_stack_ptr--;
-
-	yy_delete_buffer(YY_CURRENT_BUFFER);
-	yy_switch_to_buffer(include_stack[include_stack_ptr]);
-	yylineno = include_line[include_stack_ptr];
-}
-
-void script_PrintFileStack(void)
-{
-	int32_t i = include_stack_ptr;
-
-	include_line[i] = yylineno;
-
-	while (i > 0) {
-		fprintf(stderr, "%s(%d) -> ", include_path[i], include_line[i]);
-		i--;
-	}
-	fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]);
-}
-
-noreturn_ void script_fatalerror(const char *fmt, ...)
-{
-	va_list args;
-	va_start(args, fmt);
-	fprintf(stderr, "error: ");
-	script_PrintFileStack();
-	fprintf(stderr, ":\n    ");
-	vfprintf(stderr, fmt, args);
-	fprintf(stderr, "\n");
-	va_end(args);
-	exit(1);
-}
-
--- a/src/link/library.c
+++ /dev/null
@@ -1,122 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "extern/err.h"
-
-#include "link/mylink.h"
-#include "link/main.h"
-
-static uint8_t symboldefined(char *name)
-{
-	const struct sSection *pSect;
-
-	pSect = pSections;
-
-	while (pSect) {
-		int32_t i;
-
-		for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
-			const struct sSymbol *tSymbol = pSect->tSymbols[i];
-
-			if ((tSymbol->Type == SYM_EXPORT)
-			    || ((tSymbol->Type == SYM_LOCAL)
-				&& (pSect == tSymbol->pSection))) {
-
-				if (strcmp(tSymbol->pzName, name) == 0)
-					return 1;
-			}
-		}
-		pSect = pSect->pNext;
-	}
-	return 0;
-}
-
-static uint8_t addmodulecontaining(char *name)
-{
-	struct sSection **ppLSect = &pLibSections;
-
-	while (*ppLSect) {
-		int32_t i;
-
-		for (i = 0; i < (*ppLSect)->nNumberOfSymbols; i += 1) {
-			const struct sSymbol *tSymbol = (*ppLSect)->tSymbols[i];
-
-			if ((tSymbol->Type == SYM_EXPORT)
-			    || ((tSymbol->Type == SYM_LOCAL)
-				&& ((*ppLSect) == tSymbol->pSection))) {
-
-				if (strcmp(tSymbol->pzName, name) == 0) {
-					struct sSection **ppSect = &pSections;
-
-					while (*ppSect)
-						ppSect = &((*ppSect)->pNext);
-
-					*ppSect = *ppLSect;
-					*ppLSect = (*ppLSect)->pNext;
-					(*ppSect)->pNext = NULL;
-					return 1;
-				}
-			}
-		}
-		ppLSect = &((*ppLSect)->pNext);
-	}
-	return 0;
-}
-
-void AddNeededModules(void)
-{
-	struct sSection *pSect;
-
-	if ((options & OPT_SMART_C_LINK) == 0) {
-		struct sSection **ppLSect;
-
-		ppLSect = &pLibSections;
-
-		while (*ppLSect) {
-			struct sSection **ppSect = &pSections;
-
-			while (*ppSect)
-				ppSect = &((*ppSect)->pNext);
-
-			*ppSect = *ppLSect;
-			*ppLSect = (*ppLSect)->pNext;
-			(*ppSect)->pNext = NULL;
-
-			/* ppLSect=&((*ppLSect)->pNext); */
-		}
-		return;
-	}
-	if (options & OPT_SMART_C_LINK) {
-		if (!addmodulecontaining(smartlinkstartsymbol)) {
-			errx(1, "Can't find start symbol '%s'",
-			     smartlinkstartsymbol);
-		} else {
-			printf("Smart linking with symbol '%s'\n",
-			       smartlinkstartsymbol);
-		}
-	}
-	pSect = pSections;
-
-	while (pSect) {
-		int32_t i;
-
-		for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
-			if ((pSect->tSymbols[i]->Type == SYM_IMPORT)
-			    || (pSect->tSymbols[i]->Type == SYM_LOCAL)) {
-				if (!symboldefined(pSect->tSymbols[i]->pzName))
-					addmodulecontaining(pSect->tSymbols[i]->pzName);
-			}
-		}
-		pSect = pSect->pNext;
-	}
-}
--- a/src/link/main.c
+++ b/src/link/main.c
@@ -1,141 +1,162 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
-#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
 #include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
 #include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
 
-#include "extern/err.h"
-
 #include "link/object.h"
-#include "link/output.h"
+#include "link/symbol.h"
+#include "link/section.h"
 #include "link/assign.h"
 #include "link/patch.h"
-#include "link/mylink.h"
-#include "link/mapfile.h"
-#include "link/main.h"
-#include "link/library.h"
+#include "link/output.h"
 
+#include "extern/err.h"
 #include "version.h"
 
-enum eBlockType {
-	BLOCK_COMMENT,
-	BLOCK_OBJECTS,
-	BLOCK_LIBRARIES,
-	BLOCK_OUTPUT
-};
+bool isDmgMode;               /* -d */
+char const *linkerScriptName; /* -l */
+char const *mapFileName;      /* -m */
+char const *symFileName;      /* -n */
+char const *overlayFileName;  /* -O */
+char const *outputFileName;   /* -o */
+uint8_t padValue;             /* -p */
+bool is32kMode;               /* -t */
+bool beVerbose;               /* -v */
+bool isWRA0Mode;              /* -w */
 
-int32_t options;
-int32_t fillchar;
-char *smartlinkstartsymbol;
+FILE *openFile(char const *fileName, char const *mode)
+{
+	if (!fileName)
+		return NULL;
 
-/*
- * Print the usagescreen
- */
+	FILE *file = fopen(fileName, mode);
 
-static void print_usage(void)
+	if (!file)
+		err(1, "Could not open file \"%s\"", fileName);
+
+	return file;
+}
+
+/**
+ * Prints the program's usage to stdout.
+ */
+static void printUsage(void)
 {
-	printf(
-"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
-"               [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
-	exit(1);
+	puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
+	     "               [-o outfile] [-p pad_value] [-s symbol] file [...]");
 }
 
-/*
- * The main routine
+/**
+ * Cleans up what has been done
+ * Mostly here to please tools such as `valgrind` so actual errors can be seen
  */
+static void cleanup(void)
+{
+	obj_Cleanup();
+}
 
 int main(int argc, char *argv[])
 {
-	int ch;
-	char *ep;
+	char optionChar;
+	char *endptr; /* For error checking with `strtol` */
+	unsigned long value; /* For storing `strtoul`'s return value */
 
-	if (argc == 1)
-		print_usage();
-
-	while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) {
-		switch (ch) {
+	/* Parse options */
+	while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) {
+		switch (optionChar) {
+		case 'd':
+			isDmgMode = true;
+			isWRA0Mode = true;
+			break;
 		case 'l':
-			SetLinkerscriptName(optarg);
+			linkerScriptName = optarg;
 			break;
 		case 'm':
-			SetMapfileName(optarg);
+			mapFileName = optarg;
 			break;
 		case 'n':
-			SetSymfileName(optarg);
+			symFileName = optarg;
 			break;
-		case 'o':
-			out_Setname(optarg);
-			break;
 		case 'O':
-			out_SetOverlayname(optarg);
-			options |= OPT_OVERLAY;
+			overlayFileName = optarg;
 			break;
+		case 'o':
+			outputFileName = optarg;
+			break;
 		case 'p':
-			fillchar = strtoul(optarg, &ep, 0);
-			if (optarg[0] == '\0' || *ep != '\0')
+			value = strtoul(optarg, &endptr, 0);
+			if (optarg[0] == '\0' || *endptr != '\0')
 				errx(1, "Invalid argument for option 'p'");
-			if (fillchar < 0 || fillchar > 0xFF)
-				errx(1, "Argument for option 'p' must be between 0 and 0xFF");
+			if (value > 0xFF)
+				errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)");
+			padValue = value;
 			break;
 		case 's':
-			options |= OPT_SMART_C_LINK;
-			smartlinkstartsymbol = optarg;
+			/* FIXME: nobody knows what this does, figure it out */
+			(void)optarg;
+			warnx("Nobody has any idea what `-s` does");
 			break;
 		case 't':
-			options |= OPT_TINY;
+			is32kMode = true;
 			break;
-		case 'd':
-			/*
-			 * Set to set WRAM as a single continuous block as on
-			 * DMG. All WRAM sections must be WRAM0 as bankable WRAM
-			 * sections do not exist in this mode. A WRAMX section
-			 * will raise an error. VRAM bank 1 can't be used if
-			 * this option is enabled either.
-			 *
-			 * This option implies OPT_CONTWRAM.
-			 */
-			options |= OPT_DMG_MODE;
-			/* FALLTHROUGH */
-		case 'w':
-			/*
-			 * Set to set WRAM as a single continuous block as on
-			 * DMG. All WRAM sections must be WRAM0 as bankable WRAM
-			 * sections do not exist in this mode. A WRAMX section
-			 * will raise an error.
-			 */
-			options |= OPT_CONTWRAM;
-			break;
 		case 'V':
 			printf("rgblink %s\n", get_package_version_string());
 			exit(0);
+		case 'v':
+			beVerbose = true;
+			break;
+		case 'w':
+			isWRA0Mode = true;
+			break;
 		default:
-			print_usage();
-			/* NOTREACHED */
+			printUsage();
+			exit(1);
 		}
 	}
-	argc -= optind;
-	argv += optind;
 
-	if (argc == 0)
-		print_usage();
+	int curArgIndex = optind;
 
-	for (int32_t i = 0; i < argc; ++i)
-		obj_Readfile(argv[i]);
+	/* If no input files were specified, the user must have screwed up */
+	if (curArgIndex == argc) {
+		fprintf(stderr, "No input files");
+		printUsage();
+		exit(1);
+	}
 
-	AddNeededModules();
-	AssignSections();
-	CreateSymbolTable();
-	Patch();
-	Output();
-	CloseMapfile();
+	/* Patch the size array depending on command-line options */
+	if (is32kMode)
+		maxsize[SECTTYPE_ROM0] = 0x8000;
+	if (isWRA0Mode)
+		maxsize[SECTTYPE_WRAM0] = 0x2000;
 
-	return 0;
+	/* Patch the bank ranges array depending on command-line options */
+	if (isDmgMode)
+		bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
+
+	/* Read all object files first, */
+	while (curArgIndex < argc)
+		obj_ReadFile(argv[curArgIndex++]);
+
+	/* then process them, */
+	obj_DoSanityChecks();
+	assign_AssignSections();
+	assign_Cleanup();
+
+	/* and finally output the result. */
+	patch_ApplyPatches();
+	out_WriteFiles();
+
+	/* Do cleanup before quitting, though. */
+	cleanup();
 }
--- a/src/link/mapfile.c
+++ /dev/null
@@ -1,151 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <errno.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "extern/err.h"
-
-#include "link/assign.h"
-#include "link/main.h"
-#include "link/mylink.h"
-
-static int32_t currentbank;
-static int32_t sfbank;
-static FILE *mf;
-static FILE *sf;
-
-void SetMapfileName(char *name)
-{
-	mf = fopen(name, "w");
-
-	if (mf == NULL)
-		err(1, "Cannot open mapfile '%s'", name);
-}
-
-void SetSymfileName(char *name)
-{
-	sf = fopen(name, "w");
-
-	if (sf == NULL)
-		err(1, "Cannot open symfile '%s'", name);
-
-	fprintf(sf, "; File generated by rgblink\n\n");
-}
-
-void CloseMapfile(void)
-{
-	if (mf) {
-		fclose(mf);
-		mf = NULL;
-	}
-	if (sf) {
-		fclose(sf);
-		sf = NULL;
-	}
-}
-
-void MapfileInitBank(int32_t bank)
-{
-	if ((bank < 0) || (bank >= BANK_INDEX_MAX))
-		errx(1, "%s: Unknown bank %d\n", __func__, bank);
-
-	if (mf) {
-		currentbank = bank;
-		if (BankIndexIsROM0(bank)) {
-			fprintf(mf, "ROM Bank #0 (HOME):\n");
-		} else if (BankIndexIsROMX(bank)) {
-			fprintf(mf, "ROM Bank #%d:\n",
-				bank - BANK_INDEX_ROMX + 1);
-		} else if (BankIndexIsWRAM0(bank)) {
-			fprintf(mf, "WRAM Bank #0:\n");
-		} else if (BankIndexIsWRAMX(bank)) {
-			fprintf(mf, "WRAM Bank #%d:\n",
-				bank - BANK_INDEX_WRAMX + 1);
-		} else if (BankIndexIsVRAM(bank)) {
-			fprintf(mf, "VRAM Bank #%d:\n", bank - BANK_INDEX_VRAM);
-		} else if (BankIndexIsOAM(bank)) {
-			fprintf(mf, "OAM:\n");
-		} else if (BankIndexIsHRAM(bank)) {
-			fprintf(mf, "HRAM:\n");
-		} else if (BankIndexIsSRAM(bank)) {
-			fprintf(mf, "SRAM Bank #%d:\n", bank - BANK_INDEX_SRAM);
-		}
-	}
-	if (sf) {
-		if (BankIndexIsROM0(bank))
-			sfbank = 0;
-		else if (BankIndexIsROMX(bank))
-			sfbank = bank - BANK_INDEX_ROMX + 1;
-		else if (BankIndexIsWRAM0(bank))
-			sfbank = 0;
-		else if (BankIndexIsWRAMX(bank))
-			sfbank = bank - BANK_INDEX_WRAMX + 1;
-		else if (BankIndexIsVRAM(bank))
-			sfbank = bank - BANK_INDEX_VRAM;
-		else if (BankIndexIsOAM(bank))
-			sfbank = 0;
-		else if (BankIndexIsHRAM(bank))
-			sfbank = 0;
-		else if (BankIndexIsSRAM(bank))
-			sfbank = bank - BANK_INDEX_SRAM;
-		else
-			sfbank = 0;
-	}
-}
-
-void MapfileWriteSection(const struct sSection *pSect)
-{
-	int32_t i;
-
-	if (mf) {
-		if (pSect->nByteSize > 0) {
-			fprintf(mf, "  SECTION: $%04X-$%04X ($%04X bytes) [\"%s\"]\n",
-				pSect->nOrg, pSect->nOrg + pSect->nByteSize - 1,
-				pSect->nByteSize, pSect->pzName);
-		} else {
-			fprintf(mf, "  SECTION: $%04X ($0 bytes) [\"%s\"]\n",
-				pSect->nOrg, pSect->pzName);
-		}
-	}
-
-	for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
-		const struct sSymbol *pSym = pSect->tSymbols[i];
-
-		/* Don't print '@' */
-		if (strcmp(pSym->pzName, "@") == 0)
-			continue;
-
-		if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) {
-			if (mf) {
-				fprintf(mf, "           $%04X = %s\n",
-					pSym->nOffset + pSect->nOrg,
-					pSym->pzName);
-			}
-			if (sf) {
-				fprintf(sf, "%02X:%04X %s\n", sfbank,
-					pSym->nOffset + pSect->nOrg,
-					pSym->pzName);
-			}
-		}
-	}
-}
-
-void MapfileCloseBank(int32_t slack)
-{
-	if (!mf)
-		return;
-
-	if (slack == MaxAvail[currentbank])
-		fprintf(mf, "  EMPTY\n\n");
-	else
-		fprintf(mf, "    SLACK: $%04X bytes\n\n", slack);
-}
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -1,381 +1,500 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
-/*
- * Here we have the routines that read an objectfile
- */
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdint.h>
 #include <stdio.h>
-#include <stdlib.h>
+#include <stdint.h>
 #include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
 
-#include "common.h"
+#include "link/object.h"
+#include "link/main.h"
+#include "link/symbol.h"
+#include "link/section.h"
+#include "link/assign.h"
 
 #include "extern/err.h"
+#include "linkdefs.h"
+#include "common.h"
 
-#include "link/assign.h"
-#include "link/mylink.h"
-#include "link/main.h"
+static struct SymbolList {
+	size_t nbSymbols;
+	struct Symbol **symbolList;
+	struct SymbolList *next;
+} *symbolLists;
 
-struct sSymbol **tSymbols;
-struct sSection *pSections;
-struct sSection *pLibSections;
-uint8_t oReadLib;
+/***** Helper functions for reading object files *****/
 
 /*
- * Read 32-bit values with the correct endianness
+ * Internal, DO NOT USE.
+ * For helper wrapper macros defined below, such as `tryReadlong`
  */
-static int32_t readlong(FILE *f)
+#define tryRead(func, type, errval, var, file, ...) \
+	do { \
+		FILE *tmpFile = file; \
+		type tmpVal = func(tmpFile); \
+		if (tmpVal == (errval)) { \
+			errx(1, __VA_ARGS__, feof(tmpFile) \
+						? "Unexpected end of file" \
+						: strerror(errno)); \
+		} \
+		var = tmpVal; \
+	} while (0)
+
+/**
+ * Reads an unsigned long (32-bit) value from a file.
+ * @param file The file to read from. This will read 4 bytes from the file.
+ * @return The value read, cast to a int64_t, or -1 on failure.
+ */
+static int64_t readlong(FILE *file)
 {
-	uint32_t r;
+	uint32_t value = 0;
 
-	r = ((uint32_t)(uint8_t)fgetc(f));
-	r |= ((uint32_t)(uint8_t)fgetc(f)) << 8;
-	r |= ((uint32_t)(uint8_t)fgetc(f)) << 16;
-	r |= ((uint32_t)(uint8_t)fgetc(f)) << 24;
+	/* Read the little-endian value byte by byte */
+	for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
+		int byte = getc(file);
 
-	return (int32_t)r;
+		if (byte == EOF)
+			return INT64_MAX;
+		value |= (uint8_t)byte << shift;
+	}
+	return value;
 }
 
-/*
- * Read a NULL terminated string from a file
+/**
+ * Helper macro for reading longs from a file, and errors out if it fails to.
+ * Not as a function to avoid overhead in the general case.
+ * TODO: maybe mark the condition as `unlikely`; how to do that portably?
+ * @param var The variable to stash the number into
+ * @param file The file to read from. Its position will be advanced
+ * @param ... A format string and related arguments; note that an extra string
+ *            argument is provided, the reason for failure
  */
-int32_t readasciiz(char **dest, FILE *f)
-{
-	size_t r = 0;
+#define tryReadlong(var, file, ...) \
+	tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
 
-	size_t bufferLength = 16;
-	char *start = malloc(bufferLength);
-	char *s = start;
+/* There is no `readbyte`, just use `fgetc` or `getc`. */
 
-	if (!s)
-		err(1, "%s: Couldn't allocate memory", __func__);
+/**
+ * Helper macro for reading bytes from a file, and errors out if it fails to.
+ * Differs from `tryGetc` in that the backing function is fgetc(1).
+ * Not as a function to avoid overhead in the general case.
+ * TODO: maybe mark the condition as `unlikely`; how to do that portably?
+ * @param var The variable to stash the number into
+ * @param file The file to read from. Its position will be advanced
+ * @param ... A format string and related arguments; note that an extra string
+ *            argument is provided, the reason for failure
+ */
+#define tryFgetc(var, file, ...) \
+	tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
 
-	while (((*s++) = fgetc(f)) != 0) {
-		r += 1;
+/**
+ * Helper macro for reading bytes from a file, and errors out if it fails to.
+ * Differs from `tryGetc` in that the backing function is fgetc(1).
+ * Not as a function to avoid overhead in the general case.
+ * TODO: maybe mark the condition as `unlikely`; how to do that portably?
+ * @param var The variable to stash the number into
+ * @param file The file to read from. Its position will be advanced
+ * @param ... A format string and related arguments; note that an extra string
+ *            argument is provided, the reason for failure
+ */
+#define tryGetc(var, file, ...) \
+	tryRead(getc, int, EOF, var, file, __VA_ARGS__)
 
-		if (r >= bufferLength) {
-			bufferLength *= 2;
-			start = realloc(start, bufferLength);
-			if (!start) {
-				err(1, "%s: Couldn't allocate memory",
-				    __func__);
-			}
-			s = start + r;
-		}
-	}
-
-	*dest = start;
-	return (r + 1);
-}
-
-/*
- * Allocate a new section and link it into the list
+/**
+ * Reads a '\0'-terminated string from a file.
+ * @param file The file to read from. The file position will be advanced.
+ * @return The string read, or NULL on failure.
+ *         If a non-NULL pointer is returned, make sure to `free` it when done!
  */
-struct sSection *AllocSection(void)
+static char *readstr(FILE *file)
 {
-	struct sSection **ppSections;
+	/* Default buffer size, have it close to the average string length */
+	size_t capacity = 32 / 2;
+	size_t index = -1;
+	/* Force the first iteration to allocate */
+	char *str = NULL;
 
-	if (oReadLib == 1)
-		ppSections = &pLibSections;
-	else
-		ppSections = &pSections;
+	do {
+		/* Prepare going to next char */
+		index++;
 
-	while (*ppSections)
-		ppSections = &((*ppSections)->pNext);
+		/* If the buffer isn't suitable to write the next char... */
+		if (index >= capacity || !str) {
+			capacity *= 2;
+			str = realloc(str, capacity);
+			/* End now in case of error */
+			if (!str)
+				return NULL;
+		}
 
-	*ppSections = malloc(sizeof **ppSections);
-	if (!*ppSections)
-		err(1, "%s: Couldn't allocate memory", __func__);
+		/* Read char */
+		int byte = getc(file);
 
-	(*ppSections)->tSymbols = tSymbols;
-	(*ppSections)->pNext = NULL;
-	(*ppSections)->pPatches = NULL;
-	(*ppSections)->oAssigned = 0;
-	return *ppSections;
+		if (byte == EOF)
+			return NULL;
+		str[index] = byte;
+	} while (str[index]);
+	return str;
 }
 
-/*
- * Read a symbol from a file
+/**
+ * Helper macro for reading bytes from a file, and errors out if it fails to.
+ * Not as a function to avoid overhead in the general case.
+ * TODO: maybe mark the condition as `unlikely`; how to do that portably?
+ * @param var The variable to stash the string into
+ * @param file The file to read from. Its position will be advanced
+ * @param ... A format string and related arguments; note that an extra string
+ *            argument is provided, the reason for failure
  */
-struct sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
-{
-	struct sSymbol *pSym;
+#define tryReadstr(var, file, ...) \
+	tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
 
-	pSym = malloc(sizeof(*pSym));
-	if (!pSym)
-		err(1, "%s: Couldn't allocate memory", __func__);
+/***** Functions to parse object files *****/
 
-	readasciiz(&pSym->pzName, f);
-	pSym->Type = (enum eSymbolType)fgetc(f);
+/**
+ * Reads a RGB6 symbol from a file.
+ * @param file The file to read from
+ * @param symbol The struct to fill
+ * @param fileName The filename to report in errors
+ */
+static void readSymbol(FILE *file, struct Symbol *symbol, char const *fileName)
+{
+	tryReadstr(symbol->name, file, "%s: Cannot read symbol name: %s",
+		   fileName);
+	tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
+		fileName, symbol->name);
+	/* If the symbol is defined in this file, read its definition */
+	if (symbol->type != SYMTYPE_IMPORT) {
+		symbol->objFileName = fileName;
+		tryReadstr(symbol->fileName, file,
+			   "%s: Cannot read \"%s\"'s file name: %s",
+			   fileName, symbol->name);
+		tryReadlong(symbol->lineNo, file,
+			    "%s: Cannot read \"%s\"'s line number: %s",
+			    fileName, symbol->name);
+		tryReadlong(symbol->sectionID, file,
+			    "%s: Cannot read \"%s\"'s section ID: %s",
+			    fileName, symbol->name);
+		tryReadlong(symbol->offset, file,
+			    "%s: Cannot read \"%s\"'s value: %s",
+			    fileName, symbol->name);
+	} else {
+		symbol->sectionID = -1;
+	}
+}
 
-	pSym->pzObjFileName = tzObjectfile;
+/**
+ * Reads a RGB6 patch from a file.
+ * @param file The file to read from
+ * @param patch The struct to fill
+ * @param fileName The filename to report in errors
+ * @param i The number of the patch to report in errors
+ */
+static void readPatch(FILE *file, struct Patch *patch,
+		      char const *fileName, char const *sectName, uint32_t i)
+{
+	tryReadstr(patch->fileName, file,
+		   "%s: Unable to read \"%s\"'s patch #%u's name: %s",
+		   fileName, sectName, i);
+	tryReadlong(patch->lineNo, file,
+		    "%s: Unable to read \"%s\"'s patch #%u's line number: %s",
+		    fileName, sectName, i);
+	tryReadlong(patch->offset, file,
+		    "%s: Unable to read \"%s\"'s patch #%u's offset: %s",
+		    fileName, sectName, i);
+	tryGetc(patch->type, file,
+		"%s: Unable to read \"%s\"'s patch #%u's type: %s",
+		fileName, sectName, i);
+	tryReadlong(patch->rpnSize, file,
+		    "%s: Unable to read \"%s\"'s patch #%u's RPN size: %s",
+		    fileName, sectName, i);
 
-	if (pSym->Type != SYM_IMPORT) {
-		readasciiz(&pSym->pzFileName, f);
-		pSym->nFileLine = readlong(f);
+	uint8_t *rpnExpression =
+		malloc(sizeof(*rpnExpression) * patch->rpnSize);
+	size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression),
+				      patch->rpnSize, file);
 
-		pSym->nSectionID = readlong(f);
-		pSym->nOffset = readlong(f);
-	}
-
-	return pSym;
+	if (nbElementsRead != patch->rpnSize)
+		errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s",
+		     fileName, sectName, i);
+	patch->rpnExpression = rpnExpression;
 }
 
-/*
- * RGB object reader routines
+/**
+ * Reads a RGB6 section from a file.
+ * @param file The file to read from
+ * @param section The struct to fill
+ * @param fileName The filename to report in errors
  */
-struct sSection *obj_ReadRGBSection(FILE *f)
+static void readSection(FILE *file, struct Section *section,
+			char const *fileName)
 {
-	struct sSection *pSection;
-	char *pzName;
+	int32_t tmp;
 
-	readasciiz(&pzName, f);
-	if (IsSectionNameInUse(pzName))
-		errx(1, "Section name \"%s\" is already in use.", pzName);
+	tryReadstr(section->name, file, "%s: Cannot read section name: %s",
+		   fileName);
+	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
+		    fileName, section->name);
+	if (tmp < 0 || tmp > UINT16_MAX)
+		errx(1, "\"%s\"'s section size (%d) is invalid", section->name,
+		     tmp);
+	section->size = tmp;
+	tryGetc(section->type, file, "%s: Cannot read \"%s\"'s type: %s",
+		fileName, section->name);
+	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s",
+		    fileName, section->name);
+	section->isAddressFixed = tmp >= 0;
+	if (tmp > UINT16_MAX)
+		errx(1, "\"%s\" is too large (%d)", tmp);
+	section->org = tmp;
+	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s",
+		    fileName, section->name);
+	section->isBankFixed = tmp >= 0;
+	section->bank = tmp;
+	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment: %s",
+		    fileName, section->name);
+	section->isAlignFixed = tmp != 1;
+	section->alignMask = tmp - 1;
 
-	pSection = AllocSection();
-	pSection->pzName = pzName;
+	if (sect_HasData(section->type)) {
+		/* Ensure we never allocate 0 bytes */
+		uint8_t *data = malloc(sizeof(*data) * section->size + 1);
 
-	pSection->nByteSize = readlong(f);
-	pSection->Type = (enum eSectionType)fgetc(f);
-	pSection->nOrg = readlong(f);
-	pSection->nBank = readlong(f);
-	pSection->nAlign = readlong(f);
+		if (!data)
+			err(1, "%s: Unable to read \"%s\"'s data", fileName,
+			    section->name);
+		if (section->size) {
+			size_t nbElementsRead = fread(data, sizeof(*data),
+						      section->size, file);
+			if (nbElementsRead != section->size)
+				errx(1, "%s: Cannot read \"%s\"'s data: %s",
+				     fileName, section->name,
+				     feof(file) ? "Unexpected end of file"
+						: strerror(errno));
+		}
+		section->data = data;
 
-	if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX))
-		errx(1,  "ROMX sections can't be used with option -t.");
+		tryReadlong(section->nbPatches, file,
+			    "%s: Cannot read \"%s\"'s number of patches: %s",
+			    fileName, section->name);
 
-	if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX))
-		errx(1, "WRAMX sections can't be used with options -w or -d.");
+		struct Patch *patches =
+			malloc(sizeof(*patches) * section->nbPatches + 1);
 
-	if (options & OPT_DMG_MODE) {
-		/* WRAMX sections are checked for OPT_CONTWRAM */
-		if (pSection->Type == SECT_VRAM && pSection->nBank == 1)
-			errx(1, "VRAM bank 1 can't be used with option -d.");
+		if (!patches)
+			err(1, "%s: Unable to read \"%s\"'s patches", fileName,
+			    section->name);
+		for (uint32_t i = 0; i < section->nbPatches; i++)
+			readPatch(file, &patches[i], fileName, section->name,
+				  i);
+		section->patches = patches;
 	}
+}
 
-	uint32_t maxsize = 0;
+/**
+ * Links a symbol to a section, keeping the section's symbol list sorted.
+ * @param symbol The symbol to link
+ * @param section The section to link
+ */
+static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
+{
+	uint32_t a = 0, b = section->nbSymbols;
 
-	/* Verify that the section isn't too big */
-	switch (pSection->Type) {
-	case SECT_ROM0:
-		maxsize = (options & OPT_TINY) ? 0x8000 : 0x4000;
-		break;
-	case SECT_ROMX:
-		maxsize = 0x4000;
-		break;
-	case SECT_VRAM:
-	case SECT_SRAM:
-		maxsize = 0x2000;
-		break;
-	case SECT_WRAM0:
-		maxsize = (options & OPT_CONTWRAM) ? 0x2000 : 0x1000;
-		break;
-	case SECT_WRAMX:
-		maxsize = 0x1000;
-		break;
-	case SECT_OAM:
-		maxsize = 0xA0;
-		break;
-	case SECT_HRAM:
-		maxsize = 0x7F;
-		break;
-	default:
-		errx(1, "Section \"%s\" has an invalid section type.", pzName);
-		break;
-	}
-	if (pSection->nByteSize > maxsize) {
-		errx(1, "Section \"%s\" is bigger than the max size for that type: 0x%X > 0x%X",
-		     pzName, pSection->nByteSize, maxsize);
-	}
+	while (a != b) {
+		uint32_t c = (a + b) / 2;
 
-	/*
-	 * If the section doesn't contain data, it is ready
-	 */
-	if ((pSection->Type != SECT_ROMX) && (pSection->Type != SECT_ROM0))
-		return pSection;
-
-	/* If there is no data to read, exit */
-	if (pSection->nByteSize == 0) {
-		/* Skip number of patches */
-		readlong(f);
-		pSection->pData = NULL;
-		return pSection;
+		if (section->symbols[c]->offset > symbol->offset)
+			b = c;
+		else
+			a = c + 1;
 	}
 
-	pSection->pData = malloc(pSection->nByteSize);
-	if (!pSection->pData)
-		err(1, "%s: Couldn't allocate memory", __func__);
+	struct Symbol const *tmp = symbol;
 
-	int32_t nNumberOfPatches;
-	struct sPatch **ppPatch, *pPatch;
-
-	if (fread(pSection->pData, sizeof(uint8_t), pSection->nByteSize, f)
-	    != pSection->nByteSize) {
-		err(1, "%s: Read error", __func__);
+	for (uint32_t i = a; i <= section->nbSymbols; i++) {
+		symbol = tmp;
+		tmp = section->symbols[i];
+		section->symbols[i] = symbol;
 	}
 
-	nNumberOfPatches = readlong(f);
-	ppPatch = &pSection->pPatches;
+	section->nbSymbols++;
+}
 
-	/*
-	 * And patches...
-	 */
-	while (nNumberOfPatches--) {
-		pPatch = malloc(sizeof(*pPatch));
-		if (!pPatch)
-			err(1, "%s: Couldn't allocate memory", __func__);
+/**
+ * Reads a RGB6 object file.
+ * @param file The file to read from
+ * @param fileName The filename to report in errors
+ */
+static void readRGB6File(FILE *file, char const *fileName)
+{
+	uint32_t nbSymbols;
+	uint32_t nbSections;
 
-		*ppPatch = pPatch;
-		readasciiz(&pPatch->pzFilename, f);
-		pPatch->nLineNo = readlong(f);
-		pPatch->nOffset = readlong(f);
-		pPatch->Type = (enum ePatchType)fgetc(f);
-		pPatch->nRPNSize = readlong(f);
+	tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
+		    fileName);
+	tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
+		    fileName);
 
-		if (pPatch->nRPNSize > 0) {
-			pPatch->pRPN = malloc(pPatch->nRPNSize);
-			if (!pPatch->pRPN) {
-				err(1, "%s: Couldn't allocate memory",
-				    __func__);
-			}
+	nbSectionsToAssign += nbSections;
 
-			if (fread(pPatch->pRPN, sizeof(uint8_t),
-				  pPatch->nRPNSize, f) != pPatch->nRPNSize) {
-				errx(1, "%s: Read error", __func__);
-			}
-		} else {
-			pPatch->pRPN = NULL;
-		}
+	/* This file's symbols, kept to link sections to them */
+	struct Symbol **fileSymbols =
+		malloc(sizeof(*fileSymbols) * nbSymbols + 1);
 
-		pPatch->pNext = NULL;
-		ppPatch = &(pPatch->pNext);
-	}
+	if (!fileSymbols)
+		err(1, "Failed to get memory for %s's symbols", fileName);
 
-	return pSection;
-}
+	struct SymbolList *symbolList = malloc(sizeof(*symbolList));
 
-void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
-{
-	struct sSection *pFirstSection;
-	int32_t nNumberOfSymbols, nNumberOfSections, i;
+	if (!symbolList)
+		err(1, "Failed to register %s's symbol list", fileName);
+	symbolList->symbolList = fileSymbols;
+	symbolList->nbSymbols = nbSymbols;
+	symbolList->next = symbolLists;
+	symbolLists = symbolList;
 
-	nNumberOfSymbols = readlong(pObjfile);
-	nNumberOfSections = readlong(pObjfile);
+	uint32_t nbSymPerSect[nbSections];
 
-	/* First comes the symbols */
+	memset(nbSymPerSect, 0, sizeof(nbSymPerSect));
 
-	if (nNumberOfSymbols) {
-		tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
-		if (!tSymbols)
-			err(1, "%s: Couldn't allocate memory", __func__);
+	verbosePrint("Reading %u symbols...\n", nbSymbols);
+	for (uint32_t i = 0; i < nbSymbols; i++) {
+		/* Read symbol */
+		struct Symbol *symbol = malloc(sizeof(*symbol));
 
-		for (i = 0; i < nNumberOfSymbols; i += 1)
-			tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
-	} else {
-		tSymbols = NULL;
+		if (!symbol)
+			err(1, "%s: Couldn't create new symbol", fileName);
+		readSymbol(file, symbol, fileName);
+
+		fileSymbols[i] = symbol;
+		if (symbol->type == SYMTYPE_EXPORT)
+			sym_AddSymbol(symbol);
+		if (symbol->sectionID != -1)
+			nbSymPerSect[symbol->sectionID]++;
 	}
 
-	/* Next we have the sections */
+	/* This file's sections, stored in a table to link symbols to them */
+	struct Section *fileSections[nbSections ? nbSections : 1];
 
-	pFirstSection = NULL;
-	while (nNumberOfSections--) {
-		struct sSection *pNewSection;
+	verbosePrint("Reading %u sections...\n", nbSections);
+	for (uint32_t i = 0; i < nbSections; i++) {
+		/* Read section */
+		struct Section *section = malloc(sizeof(*section));
 
-		pNewSection = obj_ReadRGBSection(pObjfile);
-		pNewSection->nNumberOfSymbols = nNumberOfSymbols;
-		if (pFirstSection == NULL)
-			pFirstSection = pNewSection;
+		if (!section)
+			err(1, "%s: Couldn't create new section", fileName);
+		readSection(file, section, fileName);
+		section->fileSymbols = fileSymbols;
+
+		sect_AddSection(section);
+		fileSections[i] = section;
+		if (nbSymPerSect[i]) {
+			section->symbols = malloc(sizeof(*section->symbols)
+							* nbSymPerSect[i]);
+			if (!section->symbols)
+				err(1, "%s: Couldn't link to symbols");
+		} else {
+			section->symbols = NULL;
+		}
+		section->nbSymbols = 0;
 	}
 
-	/*
-	 * Fill in the pSection entry in the symbolstructure.
-	 * This REALLY needs some cleaning up... but, hey, it works
-	 */
+	/* Give symbols pointers to their sections */
+	for (uint32_t i = 0; i < nbSymbols; i++) {
+		int32_t sectionID = fileSymbols[i]->sectionID;
 
-	for (i = 0; i < nNumberOfSymbols; i += 1) {
-		struct sSection *pConvSect = pFirstSection;
-
-		if ((tSymbols[i]->Type != SYM_IMPORT) &&
-		    (tSymbols[i]->nSectionID != -1)) {
-			int32_t j = 0;
-
-			while (j != tSymbols[i]->nSectionID) {
-				j += 1;
-				pConvSect = pConvSect->pNext;
-			}
-			tSymbols[i]->pSection = pConvSect;
+		if (sectionID == -1) {
+			fileSymbols[i]->section = NULL;
 		} else {
-			tSymbols[i]->pSection = NULL;
+			fileSymbols[i]->section = fileSections[sectionID];
+			/* Give the section a pointer to the symbol as well */
+			linkSymToSect(fileSymbols[i], fileSections[sectionID]);
 		}
 	}
 }
 
-/*
- * The main objectfileloadroutine (phew)
+/**
+ * Reads an object file of any supported format
+ * @param fileName The filename to report for errors
  */
-void obj_ReadOpenFile(FILE *pObjfile, char *tzObjectfile)
+void obj_ReadFile(char const *fileName)
 {
-	char tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING) + 1];
+	FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
 
-	if (fread(tzHeader, sizeof(char), strlen(RGBDS_OBJECT_VERSION_STRING),
-		  pObjfile) != strlen(RGBDS_OBJECT_VERSION_STRING)) {
-		errx(1, "%s: Read error", tzObjectfile);
+	if (!file) {
+		err(1, "Could not open file %s", fileName);
+		return;
 	}
 
-	tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING)] = 0;
+	/* Begin by reading the magic bytes and version number */
+	uint8_t versionNumber;
+	int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING,
+				  &versionNumber);
 
-	if (strncmp(tzHeader, RGBDS_OBJECT_VERSION_STRING,
-		    strlen(RGBDS_OBJECT_VERSION_STRING)) == 0) {
-		obj_ReadRGB(pObjfile, tzObjectfile);
-	} else {
-		int32_t i;
+	if (matchedElems != 1)
+		errx(1, "\"%s\" is not a RGBDS object file", fileName);
+	/* TODO: support other versions? */
+	if (versionNumber != 6)
+		errx(1, "\"%s\" is an incompatible version %hhu object file",
+		     fileName, versionNumber);
 
-		for (i = 0; i < strlen(RGBDS_OBJECT_VERSION_STRING); i++)
-			if (!isprint(tzHeader[i]))
-				tzHeader[i] = '?';
+	verbosePrint("Reading object file %s, version %hhu\n",
+		     fileName, versionNumber);
 
-		errx(1, "%s: Invalid file or object file version [%s]",
-		     tzObjectfile, tzHeader);
-	}
+	readRGB6File(file, fileName);
+
+	fclose(file);
 }
 
-void obj_Readfile(char *tzObjectfile)
+void obj_DoSanityChecks(void)
 {
-	FILE *pObjfile;
+	sect_DoSanityChecks();
+}
 
-	if (options & OPT_SMART_C_LINK)
-		oReadLib = 1;
-	else
-		oReadLib = 0;
+static void freeSection(struct Section *section, void *arg)
+{
+	(void)arg;
 
-	pObjfile = fopen(tzObjectfile, "rb");
-	if (pObjfile == NULL)
-		err(1, "Unable to open object '%s'", tzObjectfile);
+	free(section->name);
+	if (sect_HasData(section->type)) {
+		free(section->data);
+		for (int32_t i = 0; i < section->nbPatches; i++) {
+			struct Patch *patch = &section->patches[i];
 
-	obj_ReadOpenFile(pObjfile, tzObjectfile);
-	fclose(pObjfile);
+			free(patch->fileName);
+			free(patch->rpnExpression);
+		}
+		free(section->patches);
+	}
+	free(section->symbols);
+	free(section);
+}
 
-	oReadLib = 0;
+static void freeSymbol(struct Symbol *symbol)
+{
+	free(symbol->name);
+	if (symbol->type != SYMTYPE_IMPORT)
+		free(symbol->fileName);
+	free(symbol);
 }
 
-int32_t file_Length(FILE *f)
+void obj_Cleanup(void)
 {
-	uint32_t r, p;
+	sym_CleanupSymbols();
 
-	p = ftell(f);
-	fseek(f, 0, SEEK_END);
-	r = ftell(f);
-	fseek(f, p, SEEK_SET);
+	sect_ForEach(freeSection, NULL);
+	sect_CleanupSections();
 
-	return r;
+	struct SymbolList *list = symbolLists;
+
+	while (list) {
+		for (size_t i = 0; i < list->nbSymbols; i++)
+			freeSymbol(list->symbolList[i]);
+		free(list->symbolList);
+
+		struct SymbolList *next = list->next;
+
+		free(list);
+		list = next;
+	}
 }
--- a/src/link/output.c
+++ b/src/link/output.c
@@ -1,174 +1,394 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
-#include <stdint.h>
-#include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
+#include <stdint.h>
 
+#include "link/output.h"
+#include "link/main.h"
+#include "link/section.h"
+#include "link/symbol.h"
+
 #include "extern/err.h"
 
-#include "link/mylink.h"
-#include "link/mapfile.h"
-#include "link/main.h"
-#include "link/assign.h"
+FILE *outputFile;
+FILE *overlayFile;
+FILE *symFile;
+FILE *mapFile;
 
-char *tzOutname;
-char *tzOverlayname;
+struct SortedSection {
+	struct Section const *section;
+	struct SortedSection *next;
+};
 
-int32_t MaxOverlayBank;
+static struct {
+	uint32_t nbBanks;
+	struct SortedSections {
+		struct SortedSection *sections;
+		struct SortedSection *zeroLenSections;
+	} *banks;
+} sections[SECTTYPE_INVALID];
 
-void writehome(FILE *f, FILE *f_overlay)
+void out_AddSection(struct Section const *section)
 {
-	const struct sSection *pSect;
-	uint8_t *mem;
+	static uint32_t maxNbBanks[] = {
+		[SECTTYPE_ROM0]  = 1,
+		[SECTTYPE_ROMX]  = UINT32_MAX,
+		[SECTTYPE_VRAM]  = 2,
+		[SECTTYPE_SRAM]  = UINT32_MAX,
+		[SECTTYPE_WRAM0] = 1,
+		[SECTTYPE_WRAMX] = 7,
+		[SECTTYPE_OAM]   = 1,
+		[SECTTYPE_HRAM]  = 1
+	};
 
-	mem = malloc(MaxAvail[BANK_INDEX_ROM0]);
-	if (!mem)
-		return;
+	uint32_t targetBank = section->bank - bankranges[section->type][0];
+	uint32_t minNbBanks = targetBank + 1;
 
-	if (f_overlay != NULL) {
-		fseek(f_overlay, 0L, SEEK_SET);
-		if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) !=
-		    MaxAvail[BANK_INDEX_ROM0]) {
-			warnx("Failed to read data from overlay file.");
-		}
-	} else {
-		memset(mem, fillchar, MaxAvail[BANK_INDEX_ROM0]);
-	}
-	MapfileInitBank(0);
+	if (minNbBanks > maxNbBanks[section->type])
+		errx(1, "Section \"%s\" has invalid bank range (%u > %u)",
+		     section->name, section->bank,
+		     maxNbBanks[section->type] - 1);
 
-	pSect = pSections;
-	while (pSect) {
-		if (pSect->Type == SECT_ROM0) {
-			memcpy(mem + pSect->nOrg, pSect->pData,
-			       pSect->nByteSize);
-			MapfileWriteSection(pSect);
+	if (minNbBanks > sections[section->type].nbBanks) {
+		sections[section->type].banks =
+			realloc(sections[section->type].banks,
+				sizeof(*sections[0].banks) * minNbBanks);
+		for (uint32_t i = sections[section->type].nbBanks;
+		     i < minNbBanks; i++) {
+			sections[section->type].banks[i].sections = NULL;
+			sections[section->type].banks[i].zeroLenSections = NULL;
 		}
-		pSect = pSect->pNext;
+		sections[section->type].nbBanks = minNbBanks;
 	}
+	if (!sections[section->type].banks)
+		err(1, "Failed to realloc banks");
 
-	MapfileCloseBank(area_Avail(0));
+	struct SortedSection *newSection = malloc(sizeof(*newSection));
+	struct SortedSection **ptr = section->size
+		? &sections[section->type].banks[targetBank].sections
+		: &sections[section->type].banks[targetBank].zeroLenSections;
 
-	fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
-	free(mem);
+	if (!newSection)
+		err(1, "Failed to add new section \"%s\"", section->name);
+	newSection->section = section;
+
+	while (*ptr && (*ptr)->section->org < section->org)
+		ptr = &(*ptr)->next;
+
+	newSection->next = *ptr;
+	*ptr = newSection;
 }
 
-void writebank(FILE *f, FILE *f_overlay, int32_t bank)
+/**
+ * Performs sanity checks on the overlay file.
+ */
+static void checkOverlay(void)
 {
-	const struct sSection *pSect;
-	uint8_t *mem;
+	if (!overlayFile)
+		return;
 
-	mem = malloc(MaxAvail[bank]);
-	if (!mem)
+	if (fseek(overlayFile, 0, SEEK_END) != 0) {
+		warnx("Overlay file is not seekable, cannot check if properly formed");
 		return;
+	}
 
-	if (f_overlay != NULL && bank <= MaxOverlayBank) {
-		fseek(f_overlay, bank * 0x4000, SEEK_SET);
-		if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank])
-			warnx("Failed to read data from overlay file.");
-	} else {
-		memset(mem, fillchar, MaxAvail[bank]);
+	long overlaySize = ftell(overlayFile);
+
+	if (overlaySize % 0x4000)
+		errx(1, "Overlay file must have a size multiple of 0x4000");
+
+	/* Reset back to beginning */
+	fseek(overlayFile, 0, SEEK_SET);
+
+	uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1;
+
+	if (nbOverlayBanks < 1)
+		errx(1, "Overlay must be at least 0x8000 bytes large");
+
+	if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) {
+		sections[SECTTYPE_ROMX].banks =
+			realloc(sections[SECTTYPE_ROMX].banks,
+				sizeof(*sections[SECTTYPE_ROMX].banks) *
+					nbOverlayBanks);
+		if (!sections[SECTTYPE_ROMX].banks)
+			err(1, "Failed to realloc banks for overlay");
+		sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks;
 	}
-	MapfileInitBank(bank);
+}
 
-	pSect = pSections;
-	while (pSect) {
-		if (pSect->Type == SECT_ROMX && pSect->nBank == bank) {
-			memcpy(mem + pSect->nOrg - 0x4000, pSect->pData,
-			       pSect->nByteSize);
-			MapfileWriteSection(pSect);
+/**
+ * Write a ROM bank's sections to the output file.
+ * @param bankSections The bank's sections, ordered by increasing address
+ * @param baseOffset The address of the bank's first byte in GB address space
+ * @param size The size of the bank
+ */
+static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
+		      uint16_t size)
+{
+	uint16_t offset = 0;
+
+	while (bankSections) {
+		struct Section const *section = bankSections->section;
+
+		/* Output padding up to the next SECTION */
+		while (offset + baseOffset < section->org) {
+			putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
+						  : padValue,
+				      outputFile);
+			offset++;
 		}
-		pSect = pSect->pNext;
-	}
 
-	MapfileCloseBank(area_Avail(bank));
+		/* Output the section itself */
+		fwrite(section->data, sizeof(*section->data), section->size,
+		       outputFile);
+		if (overlayFile) {
+			/* Skip bytes even with pipes */
+			for (uint16_t i = 0; i < section->size; i++)
+				getc_unlocked(overlayFile);
+		}
+		offset += section->size;
 
-	fwrite(mem, 1, MaxAvail[bank], f);
-	free(mem);
+		bankSections = bankSections->next;
+	}
+
+	while (offset < size) {
+		putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
+					  : padValue,
+			      outputFile);
+		offset++;
+	}
 }
 
-void out_Setname(char *tzOutputfile)
+/**
+ * Writes a ROM file to the output.
+ */
+static void writeROM(void)
 {
-	tzOutname = tzOutputfile;
+	outputFile = openFile(outputFileName, "wb");
+	overlayFile = openFile(overlayFileName, "rb");
+
+	checkOverlay();
+
+	if (outputFile) {
+		flockfile(outputFile);
+		if (overlayFile)
+			flockfile(overlayFile);
+
+		if (sections[SECTTYPE_ROM0].nbBanks > 0)
+			writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
+				  0x0000, 0x4000);
+
+		for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
+			writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
+				  0x4000, 0x4000);
+
+		if (overlayFile)
+			funlockfile(overlayFile);
+		funlockfile(outputFile);
+	}
+
+	closeFile(outputFile);
+	closeFile(overlayFile);
 }
 
-void out_SetOverlayname(char *tzOverlayfile)
+/**
+ * Get the lowest section by address out of the two
+ * @param s1 One choice
+ * @param s2 The other
+ * @return The lowest section of the two, or the non-NULL one if applicable
+ */
+static struct SortedSection const **nextSection(struct SortedSection const **s1,
+						struct SortedSection const **s2)
 {
-	tzOverlayname = tzOverlayfile;
+	if (!*s1)
+		return s2;
+	if (!*s2)
+		return s1;
+
+	return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
 }
 
-void Output(void)
+/**
+ * Write a bank's contents to the sym file
+ * @param bankSections The bank's sections
+ */
+static void writeSymBank(struct SortedSections const *bankSections)
 {
-	int32_t i;
-	FILE *f;
-	FILE *f_overlay = NULL;
+	if (!symFile)
+		return;
 
-	/*
-	 * Load overlay
-	 */
+	struct {
+		struct SortedSection const *sections;
+#define sect sections->section /* Fake member as a shortcut */
+		uint32_t i;
+		struct Symbol const *sym;
+		uint16_t addr;
+	} sectList = { .sections = bankSections->sections, .i = 0 },
+	zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
+	  *minSectList;
 
-	if (tzOverlayname) {
-		f_overlay = fopen(tzOverlayname, "rb");
+	for (;;) {
+		while (sectList.sections
+		    && sectList.i   == sectList.sect->nbSymbols) {
+			sectList.sections   = sectList.sections->next;
+			sectList.i   = 0;
+		}
+		while (zlSectList.sections
+		    && zlSectList.i == zlSectList.sect->nbSymbols) {
+			zlSectList.sections = zlSectList.sections->next;
+			zlSectList.i = 0;
+		}
 
-		if (!f_overlay) {
-			errx(1, "Failed to open overlay file %s\n",
-			     tzOverlayname);
+		if (!sectList.sections && !zlSectList.sections) {
+			break;
+		} else if (sectList.sections && zlSectList.sections) {
+			sectList.sym   = sectList.sect->symbols[sectList.i];
+			zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
+			sectList.addr =
+				sectList.sym->offset   + sectList.sect->org;
+			zlSectList.addr =
+				zlSectList.sym->offset + zlSectList.sect->org;
+
+			minSectList = sectList.addr < zlSectList.addr
+								? &sectList
+								: &zlSectList;
+		} else if (sectList.sections) {
+			sectList.sym   = sectList.sect->symbols[sectList.i];
+			sectList.addr   =
+				sectList.sym->offset   + sectList.sect->org;
+
+			minSectList = &sectList;
+		} else {
+			zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
+			zlSectList.addr =
+				zlSectList.sym->offset + zlSectList.sect->org;
+
+			minSectList = &zlSectList;
 		}
+		fprintf(symFile, "%02x:%04x %s\n",
+			minSectList->sect->bank, minSectList->addr,
+			minSectList->sym->name);
+		minSectList->i++;
+	}
+#undef sect
+}
 
-		fseek(f_overlay, 0, SEEK_END);
+/**
+ * Write a bank's contents to the map file
+ * @param bankSections The bank's sections
+ */
+static void writeMapBank(struct SortedSections const *sectList,
+			 enum SectionType type, uint32_t bank)
+{
+	if (!mapFile)
+		return;
 
-		if (ftell(f_overlay) % 0x4000 != 0)
-			errx(1, "Overlay file must be aligned to 0x4000 bytes.");
+	struct SortedSection const *section        = sectList->sections;
+	struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
 
-		MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
+	fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
+		bank + bankranges[type][0]);
 
-		if (MaxOverlayBank < 1)
-			errx(1, "Overlay file must be at least 0x8000 bytes.");
+	uint16_t slack = maxsize[type];
 
-		if (MaxOverlayBank > MaxBankUsed)
-			MaxBankUsed = MaxOverlayBank;
+	while (section || zeroLenSection) {
+		struct SortedSection const **pickedSection =
+			nextSection(&section, &zeroLenSection);
+		struct Section const *sect = (*pickedSection)->section;
+
+		slack -= sect->size;
+
+		fprintf(mapFile, "  SECTION: $%04x-$%04x ($%04x byte%s) [\"%s\"]\n",
+			sect->org, sect->org + sect->size - 1, sect->size,
+			sect->size == 1 ? "" : "s", sect->name);
+
+		for (size_t i = 0; i < sect->nbSymbols; i++)
+			fprintf(mapFile, "           $%04x = %s\n",
+				sect->symbols[i]->offset + sect->org,
+				sect->symbols[i]->name);
+
+		*pickedSection = (*pickedSection)->next;
 	}
 
-	/*
-	 * Write ROM.
-	 */
+	if (slack == maxsize[type])
+		fputs("  EMPTY\n\n", mapFile);
+	else
+		fprintf(mapFile, "    SLACK: $%04x byte%s\n\n", slack,
+			slack == 1 ? "" : "s");
+}
 
-	f = fopen(tzOutname, "wb");
-	if (f != NULL) {
-		writehome(f, f_overlay);
-		for (i = 1; i <= MaxBankUsed; i += 1)
-			writebank(f, f_overlay, i);
+/**
+ * Writes the sym and/or map files, if applicable.
+ */
+static void writeSymAndMap(void)
+{
+	if (!symFileName && !mapFileName)
+		return;
 
-		fclose(f);
+	enum SectionType typeMap[SECTTYPE_INVALID] = {
+		SECTTYPE_ROM0,
+		SECTTYPE_ROMX,
+		SECTTYPE_VRAM,
+		SECTTYPE_SRAM,
+		SECTTYPE_WRAM0,
+		SECTTYPE_WRAMX,
+		SECTTYPE_OAM,
+		SECTTYPE_HRAM
+	};
+
+	symFile = openFile(symFileName, "w");
+	mapFile = openFile(mapFileName, "w");
+
+	if (symFileName) {
+		fputs("; File generated by rgblink\n", symFile);
 	}
 
-	/*
-	 * Close overlay
-	 */
+	for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
+		enum SectionType type = typeMap[i];
 
-	if (tzOverlayname)
-		fclose(f_overlay);
+		if (sections[type].nbBanks > 0) {
+			for (uint32_t bank = 0; bank < sections[type].nbBanks;
+			     bank++) {
+				writeSymBank(&sections[type].banks[bank]);
+				writeMapBank(&sections[type].banks[bank],
+					     type, bank);
+			}
+		}
+	}
 
-	/*
-	 * Add regular sections to map and sym files.
-	 */
+	closeFile(symFile);
+	closeFile(mapFile);
+}
 
-	for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
-		const struct sSection *pSect;
+static void cleanupSections(struct SortedSection *section)
+{
+	while (section) {
+		struct SortedSection *next = section->next;
 
-		MapfileInitBank(i);
-		pSect = pSections;
-		while (pSect) {
-			if (pSect->nBank == i)
-				MapfileWriteSection(pSect);
-			pSect = pSect->pNext;
+		free(section);
+		section = next;
+	}
+}
+
+static void cleanup(void)
+{
+	for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
+		if (sections[type].nbBanks > 0) {
+			for (uint32_t i = 0; i < sections[type].nbBanks; i++) {
+				struct SortedSections *bank =
+					&sections[type].banks[i];
+
+				cleanupSections(bank->sections);
+				cleanupSections(bank->zeroLenSections);
+			}
+			free(sections[type].banks);
 		}
-		MapfileCloseBank(area_Avail(i));
 	}
+}
+
+void out_WriteFiles(void)
+{
+	writeROM();
+	writeSymAndMap();
+
+	cleanup();
 }
--- a/src/link/parser.y
+++ /dev/null
@@ -1,127 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-%{
-#include <stdint.h>
-#include <stdio.h>
-
-#include "extern/err.h"
-
-#include "link/script.h"
-
-int yylex(void);
-void yyerror(char *);
-
-extern int yylineno;
-%}
-
-%union {
-	int32_t i;
-	char s[512];
-}
-
-%token<i> INTEGER
-%token<s> STRING
-
-%token<s> SECTION_NONBANKED
-%token<s> SECTION_BANKED
-
-%token COMMAND_ALIGN
-%token COMMAND_ORG
-
-%token COMMAND_INCLUDE
-
-%token NEWLINE
-
-%start lines
-
-%%
-
-lines:
-	  /* empty */
-	| lines line NEWLINE
-;
-
-line:
-	  /* empty */
-	| statement
-;
-
-statement:
-	/* Statements to set the current section */
-	  SECTION_NONBANKED
-	{
-		script_SetCurrentSectionType($1, 0);
-	}
-	| SECTION_NONBANKED INTEGER
-	{
-		script_fatalerror("Trying to assign a bank to a non-banked section.\n");
-	}
-
-	| SECTION_BANKED
-	{
-		script_fatalerror("Banked section without assigned bank.\n");
-	}
-	| SECTION_BANKED INTEGER
-	{
-		script_SetCurrentSectionType($1, $2);
-	}
-
-	/* Commands to adjust the address inside the current section */
-	| COMMAND_ALIGN INTEGER
-	{
-		script_SetAlignment($2);
-	}
-	| COMMAND_ALIGN
-	{
-		script_fatalerror("ALIGN keyword needs an argument.\n");
-	}
-	| COMMAND_ORG INTEGER
-	{
-		script_SetAddress($2);
-	}
-	| COMMAND_ORG
-	{
-		script_fatalerror("ORG keyword needs an argument.\n");
-	}
-
-	/* Section name */
-	| STRING
-	{
-		script_OutputSection($1);
-	}
-
-	/* Include file */
-	| COMMAND_INCLUDE STRING
-	{
-		script_IncludeFile($2);
-	}
-
-	/* End */
-;
-
-%%
-
-extern int yylex(void);
-extern int yyparse(void);
-
-int yywrap(void)
-{
-	if (script_IncludeDepthGet() == 0)
-		return 1;
-
-	script_IncludePop();
-
-	return 0;
-}
-
-void yyerror(char *s)
-{
-	script_fatalerror("Linkerscript parse error: \"%s\"\n", s);
-}
-
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -1,344 +1,343 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
-#include <stdint.h>
-#include <stdio.h>
 #include <stdlib.h>
+#include <limits.h>
 #include <string.h>
 
-#include "extern/err.h"
-
-#include "link/assign.h"
-#include "link/main.h"
-#include "link/mylink.h"
+#include "link/patch.h"
+#include "link/section.h"
 #include "link/symbol.h"
 
-#define RPN_STACK_SIZE 256
+#include "linkdefs.h"
 
-static struct sSection *pCurrentSection;
-static int32_t rpnstack[RPN_STACK_SIZE];
-static int32_t rpnp;
-int32_t nPC;
+#include "extern/err.h"
 
-static void rpnpush(int32_t i)
-{
-	if (rpnp >= RPN_STACK_SIZE)
-		errx(1, "RPN stack overflow");
+/* This is an "empty"-type stack */
+struct RPNStack {
+	int32_t *buf;
+	size_t size;
+	size_t capacity;
+} stack;
 
-	rpnstack[rpnp] = i;
-	rpnp++;
+static inline void initRPNStack(void)
+{
+	stack.capacity = 64;
+	stack.buf = malloc(sizeof(*stack.buf) * stack.capacity);
+	if (!stack.buf)
+		err(1, "Failed to init RPN stack");
 }
 
-static int32_t rpnpop(void)
+static inline void clearRPNStack(void)
 {
-	rpnp--;
-	return rpnstack[rpnp];
+	stack.size = 0;
 }
 
-static int32_t getsymvalue(struct sPatch *pPatch, int32_t symid)
+static void pushRPN(int32_t value)
 {
-	const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
-
-	switch (tSymbol->Type) {
-	case SYM_IMPORT:
-		return sym_GetValue(pPatch, tSymbol->pzName);
-
-	case SYM_EXPORT:
-	case SYM_LOCAL:
-		if (strcmp(tSymbol->pzName, "@") == 0)
-			return nPC;
-
-		return tSymbol->nOffset + tSymbol->pSection->nOrg;
-
-	default:
-		break;
+	if (stack.size >= stack.capacity) {
+		stack.capacity *= 2;
+		stack.buf =
+			realloc(stack.buf, sizeof(*stack.buf) * stack.capacity);
+		if (!stack.buf)
+			err(1, "Failed to resize RPN stack");
 	}
 
-	errx(1, "%s: Unknown symbol type", __func__);
+	stack.buf[stack.size] = value;
+	stack.size++;
 }
 
-static int32_t getrealbankfrominternalbank(int32_t n)
+static int32_t popRPN(void)
 {
-	if (BankIndexIsWRAM0(n) || BankIndexIsROM0(n) ||
-	    BankIndexIsOAM(n)   || BankIndexIsHRAM(n)) {
-		return 0;
-	} else if (BankIndexIsROMX(n)) {
-		return n - BANK_INDEX_ROMX + 1;
-	} else if (BankIndexIsWRAMX(n)) {
-		return n - BANK_INDEX_WRAMX + 1;
-	} else if (BankIndexIsVRAM(n)) {
-		return n - BANK_INDEX_VRAM;
-	} else if (BankIndexIsSRAM(n)) {
-		return n - BANK_INDEX_SRAM;
-	}
+	if (stack.size == 0)
+		errx(1, "Internal error, RPN stack empty");
 
-	return n;
+	stack.size--;
+	return stack.buf[stack.size];
 }
 
-static int32_t getsymbank(struct sPatch *pPatch, int32_t symid)
+static inline void freeRPNStack(void)
 {
-	int32_t nBank;
-	const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
+	free(stack.buf);
+}
 
-	switch (tSymbol->Type) {
-	case SYM_IMPORT:
-		nBank = sym_GetBank(pPatch, tSymbol->pzName);
-		break;
-	case SYM_EXPORT:
-	case SYM_LOCAL:
-		nBank = tSymbol->pSection->nBank;
-		break;
-	default:
-		errx(1, "%s: Unknown symbol type", __func__);
-	}
+/* RPN operators */
 
-	return getrealbankfrominternalbank(nBank);
+static uint8_t getRPNByte(uint8_t const **expression, int32_t *size,
+			  char const *fileName, int32_t lineNo)
+{
+	if (!(*size)--)
+		errx(1, "%s(%d): RPN expression overread", fileName, lineNo);
+	return *(*expression)++;
 }
 
-int32_t calcrpn(struct sPatch *pPatch)
+/**
+ * Compute a patch's value from its RPN string.
+ * @param patch The patch to compute the value of
+ * @param section The section the patch is contained in
+ * @return The patch's value
+ */
+static int32_t computeRPNExpr(struct Patch const *patch,
+			      struct Section const *section)
 {
-	int32_t t, size;
-	uint8_t *rpn;
-	uint8_t rpn_cmd;
-	int32_t nBank;
+	uint8_t const *expression = patch->rpnExpression;
+	int32_t size = patch->rpnSize;
 
-	rpnp = 0;
+	clearRPNStack();
 
-	size = pPatch->nRPNSize;
-	rpn = pPatch->pRPN;
-	pPatch->oRelocPatch = 0;
-
 	while (size > 0) {
-		size -= 1;
-		rpn_cmd = *rpn++;
+		enum RPNCommand command = getRPNByte(&expression, &size,
+						     patch->fileName,
+						     patch->lineNo);
+		int32_t value;
 
-		switch (rpn_cmd) {
+		/*
+		 * Friendly reminder:
+		 * Be VERY careful with two `popRPN` in the same expression.
+		 * C does not guarantee order of evaluation of operands!!
+		 * So, if there are two `popRPN` in the same expression, make
+		 * sure the operation is commutative.
+		 */
+		switch (command) {
+			struct Symbol const *symbol;
+			char const *name;
+			struct Section const *sect;
+
 		case RPN_ADD:
-			rpnpush(rpnpop() + rpnpop());
+			value = popRPN() + popRPN();
 			break;
 		case RPN_SUB:
-			t = rpnpop();
-			rpnpush(rpnpop() - t);
+			value = popRPN();
+			value = popRPN() - value;
 			break;
 		case RPN_MUL:
-			rpnpush(rpnpop() * rpnpop());
+			value = popRPN() * popRPN();
 			break;
 		case RPN_DIV:
-			t = rpnpop();
-			rpnpush(rpnpop() / t);
+			value = popRPN();
+			value = popRPN() / value;
 			break;
 		case RPN_MOD:
-			t = rpnpop();
-			rpnpush(rpnpop() % t);
+			value = popRPN();
+			value = popRPN() % value;
 			break;
 		case RPN_UNSUB:
-			rpnpush(-rpnpop());
+			value = -popRPN();
 			break;
+
 		case RPN_OR:
-			rpnpush(rpnpop() | rpnpop());
+			value = popRPN() | popRPN();
 			break;
 		case RPN_AND:
-			rpnpush(rpnpop() & rpnpop());
+			value = popRPN() & popRPN();
 			break;
 		case RPN_XOR:
-			rpnpush(rpnpop() ^ rpnpop());
+			value = popRPN() ^ popRPN();
 			break;
 		case RPN_UNNOT:
-			rpnpush(~rpnpop());
+			value = ~popRPN();
 			break;
+
 		case RPN_LOGAND:
-			rpnpush(rpnpop() && rpnpop());
+			value = popRPN();
+			value = popRPN() && value;
 			break;
 		case RPN_LOGOR:
-			rpnpush(rpnpop() || rpnpop());
+			value = popRPN();
+			value = popRPN() || value;
 			break;
 		case RPN_LOGUNNOT:
-			rpnpush(!rpnpop());
+			value = !popRPN();
 			break;
+
 		case RPN_LOGEQ:
-			rpnpush(rpnpop() == rpnpop());
+			value = popRPN() == popRPN();
 			break;
 		case RPN_LOGNE:
-			rpnpush(rpnpop() != rpnpop());
+			value = popRPN() != popRPN();
 			break;
 		case RPN_LOGGT:
-			t = rpnpop();
-			rpnpush(rpnpop() > t);
+			value = popRPN();
+			value = popRPN() > value;
 			break;
 		case RPN_LOGLT:
-			t = rpnpop();
-			rpnpush(rpnpop() < t);
+			value = popRPN();
+			value = popRPN() < value;
 			break;
 		case RPN_LOGGE:
-			t = rpnpop();
-			rpnpush(rpnpop() >= t);
+			value = popRPN();
+			value = popRPN() >= value;
 			break;
 		case RPN_LOGLE:
-			t = rpnpop();
-			rpnpush(rpnpop() <= t);
+			value = popRPN();
+			value = popRPN() <= value;
 			break;
+
+		/* FIXME: sanitize shifts */
 		case RPN_SHL:
-			t = rpnpop();
-			rpnpush(rpnpop() << t);
+			value = popRPN();
+			value = popRPN() << value;
 			break;
 		case RPN_SHR:
-			t = rpnpop();
-			rpnpush(rpnpop() >> t);
+			value = popRPN();
+			value = popRPN() >> value;
 			break;
-		case RPN_HRAM:
-			t = rpnpop();
-			rpnpush(t & 0xFF);
-			if (t < 0 || (t > 0xFF && t < 0xFF00) || t > 0xFFFF) {
-				errx(1,
-				     "%s(%ld) : Value must be in the HRAM area",
-				     pPatch->pzFilename, pPatch->nLineNo);
+
+		case RPN_BANK_SYM:
+			value = 0;
+			for (uint8_t shift = 0; shift < 32; shift += 8)
+				value |= getRPNByte(&expression, &size,
+						    patch->fileName,
+						    patch->lineNo) << shift;
+
+			symbol = section->fileSymbols[value];
+
+			/* If the symbol is defined elsewhere... */
+			if (symbol->type == SYMTYPE_IMPORT) {
+				struct Symbol const *symbolDefinition =
+						sym_GetSymbol(symbol->name);
+				if (!symbolDefinition)
+					errx(1, "%s(%d): Unknown symbol \"%s\"",
+					     patch->fileName, patch->lineNo,
+					     symbol->name);
+				symbol = symbolDefinition;
 			}
+
+			value = symbol->section->bank;
 			break;
-		case RPN_CONST:
-			/* constant */
-			t = (*rpn++);
-			t |= (*rpn++) << 8;
-			t |= (*rpn++) << 16;
-			t |= (*rpn++) << 24;
-			rpnpush(t);
-			size -= 4;
+
+		case RPN_BANK_SECT:
+			name = (char const *)expression;
+			while (getRPNByte(&expression, &size, patch->fileName,
+					  patch->lineNo))
+				;
+
+			sect = sect_GetSection(name);
+
+			if (!sect)
+				errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found",
+				     patch->fileName, patch->lineNo, name);
+
+			value = sect->bank;
 			break;
-		case RPN_SYM:
-			/* symbol */
-			t = (*rpn++);
-			t |= (*rpn++) << 8;
-			t |= (*rpn++) << 16;
-			t |= (*rpn++) << 24;
-			rpnpush(getsymvalue(pPatch, t));
-			pPatch->oRelocPatch |= (getsymbank(pPatch, t) != -1);
-			size -= 4;
+
+		case RPN_BANK_SELF:
+			value = section->bank;
 			break;
-		case RPN_BANK_SYM:
-			/* symbol */
-			t = (*rpn++);
-			t |= (*rpn++) << 8;
-			t |= (*rpn++) << 16;
-			t |= (*rpn++) << 24;
-			rpnpush(getsymbank(pPatch, t));
-			size -= 4;
+
+		case RPN_HRAM:
+			value = popRPN();
+			if (value < 0
+			 || (value > 0xFF && value < 0xFF00)
+			 || value > 0xFFFF)
+				errx(1, "%s(%d): Value %d is not in HRAM range",
+				     patch->fileName, patch->lineNo, value);
+			value &= 0xFF;
 			break;
-		case RPN_BANK_SECT:
-		{
-			char *name = (char *)rpn;
 
-			struct sSection *pSection = GetSectionByName(name);
+		case RPN_CONST:
+			value = 0;
+			for (uint8_t shift = 0; shift < 32; shift += 8)
+				value |= getRPNByte(&expression, &size,
+						    patch->fileName,
+						    patch->lineNo) << shift;
+			break;
 
-			if (pSection == NULL) {
-				errx(1,
-				     "%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n",
-				     pPatch->pzFilename, pPatch->nLineNo,
-				     name);
-			}
+		case RPN_SYM:
+			value = 0;
+			for (uint8_t shift = 0; shift < 32; shift += 8)
+				value |= getRPNByte(&expression, &size,
+						    patch->fileName,
+						    patch->lineNo) << shift;
 
-			nBank = pSection->nBank;
-			rpnpush(getrealbankfrominternalbank(nBank));
+			symbol = section->fileSymbols[value];
 
-			int len = strlen(name);
+			/* If the symbol is defined elsewhere... */
+			if (symbol->type == SYMTYPE_IMPORT) {
+				struct Symbol const *symbolDefinition =
+						sym_GetSymbol(symbol->name);
+				if (!symbolDefinition)
+					errx(1, "%s(%d): Unknown symbol \"%s\"",
+					     patch->fileName, patch->lineNo,
+					     symbol->name);
+				symbol = symbolDefinition;
+			}
 
-			size -= len + 1;
-			rpn += len + 1;
+			if (!strcmp(symbol->name, "@")) {
+				value = section->org + patch->offset;
+			} else {
+				value = symbol->value;
+				/* Symbols attached to sections have offsets */
+				if (symbol->section)
+					value += symbol->section->org;
+			}
 			break;
 		}
-		case RPN_BANK_SELF:
-			nBank = pCurrentSection->nBank;
-			rpnpush(getrealbankfrominternalbank(nBank));
-			break;
-		default:
-			errx(1, "%s: Invalid command %d\n", __func__,
-			     rpn_cmd);
-			break;
-		}
+
+		pushRPN(value);
 	}
-	return rpnpop();
+
+	if (stack.size > 1)
+		warnx("%s(%d): RPN stack has %lu entries on exit, not 1",
+		      patch->fileName, patch->lineNo, stack.size);
+
+	return popRPN();
 }
 
-void Patch(void)
+/**
+ * Applies all of a section's patches
+ * @param section The section to patch
+ * @param arg Ignored callback arg
+ */
+static void applyPatches(struct Section *section, void *arg)
 {
-	struct sSection *pSect;
+	(void)arg;
 
-	pSect = pSections;
-	while (pSect) {
-		struct sPatch *pPatch;
+	if (!sect_HasData(section->type))
+		return;
 
-		pCurrentSection = pSect;
-		pPatch = pSect->pPatches;
-		while (pPatch) {
-			int32_t t;
-			int32_t nPatchOrg;
+	verbosePrint("Patching section \"%s\"...\n", section->name);
+	for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) {
+		struct Patch *patch = &section->patches[patchID];
+		int32_t value = computeRPNExpr(patch, section);
 
-			nPC = pSect->nOrg + pPatch->nOffset;
-			t = calcrpn(pPatch);
-			switch (pPatch->Type) {
-			case PATCH_BYTE:
-				if (t >= -128 && t <= 255) {
-					t &= 0xFF;
-					pSect->pData[pPatch->nOffset] =
-						(uint8_t)t;
-				} else {
-					errx(1,
-					     "%s(%ld) : Value must be 8-bit",
-					     pPatch->pzFilename,
-					     pPatch->nLineNo);
-				}
-				break;
-			case PATCH_WORD_L:
-				if (t >= -32768 && t <= 65535) {
-					t &= 0xFFFF;
-					pSect->pData[pPatch->nOffset] =
-						t & 0xFF;
-					pSect->pData[pPatch->nOffset + 1] =
-						(t >> 8) & 0xFF;
-				} else {
-					errx(1,
-					     "%s(%ld) : Value must be 16-bit",
-					     pPatch->pzFilename,
-					     pPatch->nLineNo);
-				}
-				break;
-			case PATCH_LONG_L:
-				pSect->pData[pPatch->nOffset + 0] = t & 0xFF;
-				pSect->pData[pPatch->nOffset + 1] =
-					(t >> 8) & 0xFF;
-				pSect->pData[pPatch->nOffset + 2] =
-					(t >> 16) & 0xFF;
-				pSect->pData[pPatch->nOffset + 3] =
-					(t >> 24) & 0xFF;
-				break;
-			case PATCH_BYTE_JR:
-				/* Calculate absolute address of the patch */
-				nPatchOrg = pSect->nOrg + pPatch->nOffset;
+		if (patch->type == PATCHTYPE_JR) {
+			/* `jr` is quite unlike the others... */
+			uint16_t address = section->org + patch->offset;
+			/* Target is relative to the byte *after* the operand */
+			int32_t offset = value - (address + 1);
 
-				/* t contains the destination of the jump */
-				t = (int16_t)((t & 0xFFFF) - (nPatchOrg + 1));
+			if (offset < -128 || offset > 127)
+				errx(1, "%s(%d): jr target out of reach (%d)",
+				     patch->fileName, patch->lineNo, offset);
+			section->data[patch->offset] = offset & 0xFF;
+		} else {
+			/* Patch a certain number of bytes */
+			struct {
+				uint8_t size;
+				int32_t min;
+				int32_t max;
+			} const types[] = {
+				[PATCHTYPE_BYTE] = {1,      -128,       255},
+				[PATCHTYPE_WORD] = {2,    -32768,     65536},
+				[PATCHTYPE_LONG] = {4, INT32_MIN, INT32_MAX}
+			};
 
-				if (t >= -128 && t <= 127) {
-					t &= 0xFF;
-					pSect->pData[pPatch->nOffset] =
-						(uint8_t)t;
-				} else {
-					errx(1,
-					     "%s(%ld) : Value must be 8-bit",
-					     pPatch->pzFilename,
-					     pPatch->nLineNo);
-				}
-				break;
-			default:
-				errx(1, "%s: Internal error.", __func__);
+			if (value < types[patch->type].min
+			 || value > types[patch->type].max)
+				errx(1, "%s(%d): Value %#x%s is not %u-bit",
+				     patch->fileName, patch->lineNo, value,
+				     value < 0 ? " (maybe negative?)" : "",
+				     types[patch->type].size * 8);
+			for (uint8_t i = 0; i < types[patch->type].size; i++) {
+				section->data[patch->offset + i] = value & 0xFF;
+				value >>= 8;
 			}
-
-			pPatch = pPatch->pNext;
 		}
-
-		pSect = pSect->pNext;
 	}
+}
+
+void patch_ApplyPatches(void)
+{
+	initRPNStack();
+	sect_ForEach(applyPatches, NULL);
+	freeRPNStack();
 }
--- a/src/link/script.c
+++ b/src/link/script.c
@@ -1,237 +1,516 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
+#include <stdlib.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <string.h>
+#include <ctype.h>
 
+#include "link/main.h"
+#include "link/script.h"
+#include "link/section.h"
+
 #include "extern/err.h"
 
-#include "link/assign.h"
-#include "link/mylink.h"
+FILE *linkerScript;
 
+static uint32_t lineNo;
+
 static struct {
-	uint32_t address; /* current address to write sections to */
-	uint32_t top_address; /* not inclusive */
-	enum eSectionType type;
-} bank[BANK_INDEX_MAX];
+	FILE *file;
+	uint32_t lineNo;
+	char const *name;
+} *fileStack;
 
-static int32_t current_bank = -1; /* Bank as seen by the bank array */
-static int32_t current_real_bank = -1; /* bank as seen by the GB */
+static uint32_t fileStackSize;
+static uint32_t fileStackIndex;
 
-/* Current section attributes */
-static int32_t fix_org = -1;
-static int32_t fix_align = 1;
-
-void script_InitSections(void)
+static void pushFile(char const *newFileName)
 {
-	int32_t i;
+	if (fileStackIndex == UINT32_MAX)
+		errx(1, "%s(%u): INCLUDE recursion limit reached",
+		     linkerScriptName, lineNo);
 
-	for (i = 0; i < BANK_INDEX_MAX; i++) {
-		if (BankIndexIsROM0(i)) {
-			/* ROM0 bank */
-			bank[i].address = 0x0000;
-			if (options & OPT_TINY)
-				bank[i].top_address = 0x8000;
-			else
-				bank[i].top_address = 0x4000;
-			bank[i].type = SECT_ROM0;
-		} else if (BankIndexIsROMX(i)) {
-			/* Swappable ROM bank */
-			bank[i].address = 0x4000;
-			bank[i].top_address = 0x8000;
-			bank[i].type = SECT_ROMX;
-		} else if (BankIndexIsWRAM0(i)) {
-			/* WRAM */
-			bank[i].address = 0xC000;
-			if (options & OPT_CONTWRAM)
-				bank[i].top_address = 0xE000;
-			else
-				bank[i].top_address = 0xD000;
-			bank[i].type = SECT_WRAM0;
-		} else if (BankIndexIsSRAM(i)) {
-			/* Swappable SRAM bank */
-			bank[i].address = 0xA000;
-			bank[i].top_address = 0xC000;
-			bank[i].type = SECT_SRAM;
-		} else if (BankIndexIsWRAMX(i)) {
-			/* Swappable WRAM bank */
-			bank[i].address = 0xD000;
-			bank[i].top_address = 0xE000;
-			bank[i].type = SECT_WRAMX;
-		} else if (BankIndexIsVRAM(i)) {
-			/* Swappable VRAM bank */
-			bank[i].address = 0x8000;
-			bank[i].type = SECT_VRAM;
-			if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) {
-				/* In DMG the only available bank is bank 0. */
-				bank[i].top_address = 0x8000;
-			} else {
-				bank[i].top_address = 0xA000;
-			}
-		} else if (BankIndexIsOAM(i)) {
-			/* OAM */
-			bank[i].address = 0xFE00;
-			bank[i].top_address = 0xFEA0;
-			bank[i].type = SECT_OAM;
-		} else if (BankIndexIsHRAM(i)) {
-			/* HRAM */
-			bank[i].address = 0xFF80;
-			bank[i].top_address = 0xFFFF;
-			bank[i].type = SECT_HRAM;
-		} else {
-			errx(1, "%s: Unknown bank type %d", __func__, i);
-		}
+	if (fileStackIndex == fileStackSize) {
+		if (!fileStackSize) /* Init file stack */
+			fileStackSize = 4;
+		fileStackSize *= 2;
+		fileStack = realloc(fileStack,
+				    sizeof(*fileStack) * fileStackSize);
+		if (!fileStack)
+			err(1, "%s(%u): Internal INCLUDE error",
+			    linkerScriptName, lineNo);
 	}
+
+	fileStack[fileStackIndex].file = linkerScript;
+	fileStack[fileStackIndex].lineNo = lineNo;
+	fileStack[fileStackIndex].name = linkerScriptName;
+	fileStackIndex++;
+
+	linkerScript = fopen(newFileName, "r");
+	if (!linkerScript)
+		err(1, "%s(%u): Could not open \"%s\"",
+		    linkerScriptName, lineNo, newFileName);
+	lineNo = 1;
+	linkerScriptName = newFileName;
 }
 
-void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
+static bool popFile(void)
 {
-	if (strcmp(type, "ROM0") == 0) {
-		if (bank_num != 0)
-			errx(1, "Trying to assign a bank number to ROM0.\n");
-		current_bank = BANK_INDEX_ROM0;
-		current_real_bank = 0;
-		return;
-	} else if (strcmp(type, "ROMX") == 0) {
-		if (bank_num == 0)
-			errx(1, "ROMX index can't be 0.\n");
-		if (bank_num > BANK_COUNT_ROMX) {
-			errx(1, "ROMX index too big (%d > %d).\n", bank_num,
-			     BANK_COUNT_ROMX);
-		}
-		current_bank = BANK_INDEX_ROMX + bank_num - 1;
-		current_real_bank = bank_num;
-		return;
-	} else if (strcmp(type, "VRAM") == 0) {
-		if (bank_num >= BANK_COUNT_VRAM) {
-			errx(1, "VRAM index too big (%d >= %d).\n", bank_num,
-			     BANK_COUNT_VRAM);
-		}
-		current_bank = BANK_INDEX_VRAM + bank_num;
-		current_real_bank = bank_num;
-		return;
-	} else if (strcmp(type, "WRAM0") == 0) {
-		if (bank_num != 0)
-			errx(1, "Trying to assign a bank number to WRAM0.\n");
+	if (!fileStackIndex)
+		return false;
+
+	fileStackIndex--;
+	linkerScript = fileStack[fileStackIndex].file;
+	lineNo = fileStack[fileStackIndex].lineNo;
+	linkerScriptName = fileStack[fileStackIndex].name;
+
+	return true;
+}
+
+static inline bool isWhiteSpace(int c)
+{
+	return c == ' ' || c == '\t';
+}
+
+static inline bool isNewline(int c)
+{
+	return c == '\r' || c == '\n';
+}
+
+/**
+ * Try parsing a number, in base 16 if it begins with a dollar,
+ * in base 10 otherwise
+ * @param str The number to parse
+ * @param number A pointer where the number will be written to
+ * @return True if parsing was successful, false otherwise
+ */
+static bool tryParseNumber(char const *str, uint32_t *number)
+{
+	static char const digits[] = {
+		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+		'A', 'B', 'C', 'D', 'E', 'F'
+	};
+	uint8_t base = 10;
+
+	if (*str == '$') {
+		str++;
+		base = 16;
+	}
+
+	/* An empty string is not a number */
+	if (!*str)
+		return false;
+
+	*number = 0;
+	do {
+		char chr = toupper(*str++);
+		uint8_t digit = 0;
 
-		current_bank = BANK_INDEX_WRAM0;
-		current_real_bank = 0;
-		return;
-	} else if (strcmp(type, "WRAMX") == 0) {
-		if (bank_num == 0)
-			errx(1, "WRAMX index can't be 0.\n");
-		if (bank_num > BANK_COUNT_WRAMX) {
-			errx(1, "WRAMX index too big (%d > %d).\n", bank_num,
-			     BANK_COUNT_WRAMX);
+		while (digit < base) {
+			if (chr == digits[digit])
+				break;
+			digit++;
+		}
+		if (digit == base)
+			return false;
+		*number = *number * base + digit;
+	} while (*str);
+
+	return true;
+}
+
+enum LinkerScriptTokenType {
+	TOKEN_NEWLINE,
+	TOKEN_COMMAND,
+	TOKEN_BANK,
+	TOKEN_INCLUDE,
+	TOKEN_NUMBER,
+	TOKEN_STRING,
+	TOKEN_EOF,
+
+	TOKEN_INVALID
+};
+
+char const *tokenTypes[] = {
+	[TOKEN_NEWLINE] = "newline",
+	[TOKEN_COMMAND] = "command",
+	[TOKEN_BANK]    = "bank command",
+	[TOKEN_NUMBER]  = "number",
+	[TOKEN_STRING]  = "string",
+	[TOKEN_EOF]     = "end of file"
+};
+
+enum LinkerScriptCommand {
+	COMMAND_ORG,
+	COMMAND_ALIGN,
+
+	COMMAND_INVALID
+};
+
+struct LinkerScriptToken {
+	enum LinkerScriptTokenType type;
+	union LinkerScriptTokenAttr {
+		enum LinkerScriptCommand command;
+		enum SectionType secttype;
+		uint32_t number;
+		char *string;
+	} attr;
+};
+
+static char const * const commands[] = {
+	[COMMAND_ORG] = "ORG",
+	[COMMAND_ALIGN] = "ALIGN"
+};
+
+static int readChar(FILE *file)
+{
+	int curchar = getc_unlocked(file);
+
+	if (curchar == EOF && ferror(file))
+		err(1, "%s(%u): Unexpected error in %s", linkerScriptName,
+		    lineNo, __func__);
+	return curchar;
+}
+
+static struct LinkerScriptToken const *nextToken(void)
+{
+	static struct LinkerScriptToken token;
+	int curchar;
+
+	/* If the token has a string, make sure to avoid leaking it */
+	if (token.type == TOKEN_STRING)
+		free(token.attr.string);
+
+	/* Skip initial whitespace... */
+	do
+		curchar = readChar(linkerScript);
+	while (isWhiteSpace(curchar));
+
+	/* If this is a comment, skip to the end of the line */
+	if (curchar == ';') {
+		do
+			curchar = readChar(linkerScript);
+		while (!isNewline(curchar) && curchar != EOF);
+	}
+
+	if (curchar == EOF) {
+		token.type = TOKEN_EOF;
+	} else if (isNewline(curchar)) {
+		/* If we have a newline char, this is a newline token */
+		token.type = TOKEN_NEWLINE;
+
+		/* FIXME: This works with CRLF newlines, but not CR-only */
+		if (curchar == '\r')
+			readChar(linkerScript); /* Read and discard LF */
+	} else if (curchar == '"') {
+		/* If we have a string start, this is a string */
+		token.type = TOKEN_STRING;
+		token.attr.string = NULL; /* Force initial alloc */
+
+		size_t size = 0;
+		size_t capacity = 16; /* Half of the default capacity */
+
+		do {
+			curchar = readChar(linkerScript);
+			if (curchar == EOF || isNewline(curchar))
+				errx(1, "%s(%u): Unterminated string",
+				     linkerScriptName, lineNo);
+			else if (curchar == '"')
+				/* Quotes force a string termination */
+				curchar = '\0';
+
+			if (size >= capacity || token.attr.string == NULL) {
+				capacity *= 2;
+				token.attr.string = realloc(token.attr.string,
+							    capacity);
+				if (!token.attr.string)
+					err(1, "%s: Failed to allocate memory for string",
+					    __func__);
+			}
+			token.attr.string[size++] = curchar;
+		} while (curchar);
+	} else {
+		/* This is either a number, command or bank, that is: a word */
+		char *str = NULL;
+		size_t size = 0;
+		size_t capacity = 8; /* Half of the default capacity */
+
+		for (;;) {
+			if (size >= capacity || str == NULL) {
+				capacity *= 2;
+				str = realloc(str, capacity);
+				if (!str)
+					err(1, "%s: Failed to allocate memory for token",
+					    __func__);
+			}
+			str[size] = toupper(curchar);
+			size++;
+
+			if (!curchar)
+				break;
+
+			curchar = readChar(linkerScript);
+			/* Whitespace, a newline or a comment end the token */
+			if (isWhiteSpace(curchar) || isNewline(curchar)
+			 || curchar == ';') {
+				ungetc(curchar, linkerScript);
+				curchar = '\0';
+			}
 		}
-		current_bank = BANK_INDEX_WRAMX + bank_num - 1;
-		current_real_bank = bank_num;
-		return;
-	} else if (strcmp(type, "SRAM") == 0) {
-		if (bank_num >= BANK_COUNT_SRAM) {
-			errx(1, "SRAM index too big (%d >= %d).\n", bank_num,
-			     BANK_COUNT_SRAM);
+
+		token.type = TOKEN_INVALID;
+
+		/* Try to match a command */
+		for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) {
+			if (!strcmp(commands[i], str)) {
+				token.type = TOKEN_COMMAND;
+				token.attr.command = i;
+				break;
+			}
 		}
-		current_bank = BANK_INDEX_SRAM + bank_num;
-		current_real_bank = bank_num;
-		return;
-	} else if (strcmp(type, "OAM") == 0) {
-		if (bank_num != 0) {
-			errx(1, "%s: Trying to assign a bank number to OAM.\n",
-			     __func__);
+
+		if (token.type == TOKEN_INVALID) {
+			/* Try to match a bank specifier */
+			for (enum SectionType type = 0; type < SECTTYPE_INVALID;
+			     type++) {
+				if (!strcmp(typeNames[type], str)) {
+					token.type = TOKEN_BANK;
+					token.attr.secttype = type;
+					break;
+				}
+			}
 		}
-		current_bank = BANK_INDEX_OAM;
-		current_real_bank = 0;
-		return;
-	} else if (strcmp(type, "HRAM") == 0) {
-		if (bank_num != 0) {
-			errx(1, "%s: Trying to assign a bank number to HRAM.\n",
-			     __func__);
+
+		if (token.type == TOKEN_INVALID) {
+			/* Try to match an include token */
+			if (!strcmp("INCLUDE", str))
+				token.type = TOKEN_INCLUDE;
 		}
-		current_bank = BANK_INDEX_HRAM;
-		current_real_bank = 0;
-		return;
+
+		if (token.type == TOKEN_INVALID) {
+			/* None of the strings matched, do we have a number? */
+			if (tryParseNumber(str, &token.attr.number))
+				token.type = TOKEN_NUMBER;
+			else
+				errx(1, "%s(%u): Unknown token \"%s\"",
+				     linkerScriptName, lineNo, str);
+		}
+
+		free(str);
 	}
 
-	errx(1, "%s: Unknown section type \"%s\".\n", __func__, type);
+	return &token;
 }
 
-void script_SetAddress(uint32_t addr)
+static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
+			   uint16_t *pc)
 {
-	if (current_bank == -1)
-		errx(1, "Trying to set an address without assigned bank\n");
+	switch (command) {
+	case COMMAND_INVALID:
+		trap_;
 
-	/* Make sure that we don't go back. */
-	if (bank[current_bank].address > addr) {
-		errx(1, "Trying to go to a previous address (0x%04X to 0x%04X)\n",
-		     bank[current_bank].address, addr);
-	}
+	case COMMAND_ORG:
+		*pc = arg;
+		break;
 
-	bank[current_bank].address = addr;
-
-	/* Make sure we don't overflow */
-	if (bank[current_bank].address >= bank[current_bank].top_address) {
-		errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
-		     bank[current_bank].address,
-		     bank[current_bank].top_address);
+	case COMMAND_ALIGN:
+		if (arg >= 16)
+			arg = 0;
+		else
+			arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1);
 	}
 
-	fix_org = addr;
+	if (arg < *pc)
+		errx(1, "%s(%u): `%s` cannot be used to go backwards",
+		     linkerScriptName, lineNo, commands[command]);
+	*pc = arg;
 }
 
-void script_SetAlignment(uint32_t alignment)
+enum LinkerScriptParserState {
+	PARSER_FIRSTTIME,
+	PARSER_LINESTART,
+	PARSER_INCLUDE, /* After an INCLUDE token */
+	PARSER_LINEEND
+};
+
+/* Part of internal state, but has data that needs to be freed */
+static uint16_t *curaddr[SECTTYPE_INVALID];
+
+/* Put as global to ensure it's initialized only once */
+static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME;
+
+struct SectionPlacement *script_NextSection(void)
 {
-	if (current_bank == -1)
-		errx(1, "Trying to set an alignment without assigned bank\n");
+	static struct SectionPlacement section;
+	static enum SectionType type;
+	static uint32_t bank;
+	static uint32_t bankID;
 
-	if (alignment > 15)
-		errx(1, "Trying to set an alignment too big: %d\n", alignment);
+	if (parserState == PARSER_FIRSTTIME) {
+		lineNo = 1;
 
-	uint32_t size = 1 << alignment;
-	uint32_t mask = size - 1;
+		/* Init PC for all banks */
+		for (enum SectionType i = 0; i < SECTTYPE_INVALID; i++) {
+			curaddr[i] = malloc(sizeof(*curaddr[i]) * nbbanks(i));
+			for (uint32_t b = 0; b < nbbanks(i); b++)
+				curaddr[i][b] = startaddr[i];
+		}
 
-	if (bank[current_bank].address & mask) {
-		bank[current_bank].address &= ~mask;
-		bank[current_bank].address += size;
-	}
+		type = SECTTYPE_INVALID;
 
-	/* Make sure we don't overflow */
-	if (bank[current_bank].address >= bank[current_bank].top_address) {
-		errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
-		     bank[current_bank].address,
-		     bank[current_bank].top_address);
+		parserState = PARSER_LINESTART;
 	}
 
-	fix_align = size;
-}
+	for (;;) {
+		struct LinkerScriptToken const *token = nextToken();
+		enum LinkerScriptTokenType tokType;
+		union LinkerScriptTokenAttr attr;
+		bool hasArg;
+		uint32_t arg;
 
-void script_OutputSection(const char *section_name)
-{
-	if (current_bank == -1) {
-		errx(1, "Trying to place section \"%s\" without assigned bank\n",
-		     section_name);
-	}
+		if (type != SECTTYPE_INVALID) {
+			if (curaddr[type][bankID] > endaddr(type) + 1)
+				errx(1, "%s(%u): Sections would extend past the end of %s ($%04hx > $%04hx)",
+				     linkerScriptName, lineNo, typeNames[type],
+				     curaddr[type][bankID], endaddr(type));
+			if (curaddr[type][bankID] < startaddr[type])
+				errx(1, "%s(%u): PC underflowed ($%04hx < $%04hx)",
+				     linkerScriptName, lineNo,
+				     curaddr[type][bankID], startaddr[type]);
+		}
 
-	if (!IsSectionSameTypeBankAndAttrs(section_name,
-					   bank[current_bank].type,
-					   current_real_bank,
-					   fix_org,
-					   fix_align)) {
-		errx(1, "Different attributes for \"%s\" in source and linkerscript\n",
-		     section_name);
-	}
+		switch (parserState) {
+		case PARSER_FIRSTTIME:
+			trap_;
 
-	/* Move section to its place. */
-	bank[current_bank].address +=
-		AssignSectionAddressAndBankByName(section_name,
-						  bank[current_bank].address,
-						  current_real_bank);
+		case PARSER_LINESTART:
+			switch (token->type) {
+			case TOKEN_INVALID:
+				trap_;
 
-	fix_org = -1;
-	fix_align = 1;
+			case TOKEN_EOF:
+				if (!popFile())
+					return NULL;
+				parserState = PARSER_LINEEND;
+				break;
+
+			case TOKEN_NUMBER:
+				errx(1, "%s(%u): stray number \"%u\"",
+				     linkerScriptName, lineNo,
+				     token->attr.number);
+
+			case TOKEN_NEWLINE:
+				lineNo++;
+				break;
+
+			/* A stray string is a section name */
+			case TOKEN_STRING:
+				parserState = PARSER_LINEEND;
+
+				if (type == SECTTYPE_INVALID)
+					errx(1, "%s(%u): Didn't specify a location before the section",
+					     linkerScriptName, lineNo);
+
+				section.section =
+					sect_GetSection(token->attr.string);
+				section.org = curaddr[type][bankID];
+				section.bank = bank;
+
+				curaddr[type][bankID] += section.section->size;
+				return &section;
+
+			case TOKEN_COMMAND:
+			case TOKEN_BANK:
+				tokType = token->type;
+				attr = token->attr;
+
+				token = nextToken();
+				hasArg = token->type == TOKEN_NUMBER;
+				/*
+				 * Leaving `arg` uninitialized when `!hasArg`
+				 * causes GCC to warn about its use as an
+				 * argument to `processCommand`. This cannot
+				 * happen because `hasArg` has to be true, but
+				 * silence the warning anyways.
+				 * I dislike doing this because it could swallow
+				 * actual errors, but I don't have a choice.
+				 */
+				arg = hasArg ? token->attr.number : 0;
+
+				if (tokType == TOKEN_COMMAND) {
+					if (type == SECTTYPE_INVALID)
+						errx(1, "%s(%u): Didn't specify a location before the command",
+						     linkerScriptName, lineNo);
+					if (!hasArg)
+						errx(1, "%s(%u): Command specified without an argument",
+						     linkerScriptName, lineNo);
+
+					processCommand(attr.command, arg,
+						       &curaddr[type][bankID]);
+				} else { /* TOKEN_BANK */
+					type = attr.secttype;
+					/*
+					 * If there's only one bank,
+					 * specifying the number is optional.
+					 */
+					if (!hasArg && nbbanks(type) != 1)
+						errx(1, "%s(%u): Didn't specify a bank number",
+						     linkerScriptName, lineNo);
+					else if (!hasArg)
+						arg = bankranges[type][0];
+					else if (arg < bankranges[type][0])
+						errx(1, "%s(%u): specified bank number is too low (%u < %u)",
+						     linkerScriptName, lineNo,
+						     arg, bankranges[type][0]);
+					else if (arg > bankranges[type][1])
+						errx(1, "%s(%u): specified bank number is too high (%u > %u)",
+						     linkerScriptName, lineNo,
+						     arg, bankranges[type][1]);
+					bank = arg;
+					bankID = arg - bankranges[type][0];
+				}
+
+				/* If we read a token we shouldn't have... */
+				if (token->type != TOKEN_NUMBER)
+					goto lineend;
+				break;
+
+			case TOKEN_INCLUDE:
+				parserState = PARSER_INCLUDE;
+				break;
+			}
+			break;
+
+		case PARSER_INCLUDE:
+			if (token->type != TOKEN_STRING)
+				errx(1, "%s(%u): Expected a file name after INCLUDE",
+				     linkerScriptName, lineNo);
+
+			/* Switch to that file */
+			pushFile(token->attr.string);
+
+			parserState = PARSER_LINESTART;
+			break;
+
+		case PARSER_LINEEND:
+lineend:
+			lineNo++;
+			parserState = PARSER_LINESTART;
+			if (token->type == TOKEN_EOF) {
+				if (!popFile())
+					return NULL;
+				parserState = PARSER_LINEEND;
+			} else if (token->type != TOKEN_NEWLINE)
+				errx(1, "%s(%u): Unexpected %s at the end of the line",
+				     linkerScriptName, lineNo,
+				     tokenTypes[token->type]);
+			break;
+		}
+	}
+}
+
+void script_Cleanup(void)
+{
+	for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++)
+		free(curaddr[type]);
 }
--- /dev/null
+++ b/src/link/section.c
@@ -1,0 +1,168 @@
+
+#include <stdbool.h>
+
+#include "link/main.h"
+#include "link/section.h"
+
+#include "extern/err.h"
+
+#include "hashmap.h"
+#include "common.h"
+
+uint16_t startaddr[] = {
+	[SECTTYPE_ROM0]  = 0x0000,
+	[SECTTYPE_ROMX]  = 0x4000,
+	[SECTTYPE_VRAM]  = 0x8000,
+	[SECTTYPE_SRAM]  = 0xA000,
+	[SECTTYPE_WRAM0] = 0xC000,
+	[SECTTYPE_WRAMX] = 0xD000,
+	[SECTTYPE_OAM]   = 0xFE00,
+	[SECTTYPE_HRAM]  = 0xFF80
+};
+
+uint16_t maxsize[] = {
+	[SECTTYPE_ROM0]  = 0x4000,
+	[SECTTYPE_ROMX]  = 0x4000,
+	[SECTTYPE_VRAM]  = 0x2000,
+	[SECTTYPE_SRAM]  = 0x2000,
+	[SECTTYPE_WRAM0] = 0x1000,
+	[SECTTYPE_WRAMX] = 0x1000,
+	[SECTTYPE_OAM]   = 0x00A0,
+	[SECTTYPE_HRAM]  = 0x007F
+};
+
+uint32_t bankranges[][2] = {
+	[SECTTYPE_ROM0]  = {BANK_MIN_ROM0,  BANK_MAX_ROM0},
+	[SECTTYPE_ROMX]  = {BANK_MIN_ROMX,  BANK_MAX_ROMX},
+	[SECTTYPE_VRAM]  = {BANK_MIN_VRAM,  BANK_MAX_VRAM},
+	[SECTTYPE_SRAM]  = {BANK_MIN_SRAM,  BANK_MAX_SRAM},
+	[SECTTYPE_WRAM0] = {BANK_MIN_WRAM0, BANK_MAX_WRAM0},
+	[SECTTYPE_WRAMX] = {BANK_MIN_WRAMX, BANK_MAX_WRAMX},
+	[SECTTYPE_OAM]   = {BANK_MIN_OAM,   BANK_MAX_OAM},
+	[SECTTYPE_HRAM]  = {BANK_MIN_HRAM,  BANK_MAX_HRAM}
+};
+
+char const * const typeNames[] = {
+	[SECTTYPE_ROM0]  = "ROM0",
+	[SECTTYPE_ROMX]  = "ROMX",
+	[SECTTYPE_VRAM]  = "VRAM",
+	[SECTTYPE_SRAM]  = "SRAM",
+	[SECTTYPE_WRAM0] = "WRAM0",
+	[SECTTYPE_WRAMX] = "WRAMX",
+	[SECTTYPE_OAM]   = "OAM",
+	[SECTTYPE_HRAM]  = "HRAM"
+};
+
+HashMap sections;
+
+struct ForEachArg {
+	void (*callback)(struct Section *section, void *arg);
+	void *arg;
+};
+
+static void forEach(void *section, void *arg)
+{
+	struct ForEachArg *callbackArg = (struct ForEachArg *)arg;
+
+	callbackArg->callback((struct Section *)section, callbackArg->arg);
+}
+
+void sect_ForEach(void (*callback)(struct Section *, void *), void *arg)
+{
+	struct ForEachArg callbackArg = { .callback = callback, .arg = arg};
+
+	hash_ForEach(sections, forEach, &callbackArg);
+}
+
+void sect_AddSection(struct Section *section)
+{
+	/* Check if the section already exists */
+	if (hash_GetElement(sections, section->name))
+		errx(1, "Section name \"%s\" is already in use", section->name);
+
+	/* If not, add it */
+	bool collided = hash_AddElement(sections, section->name, section);
+
+	if (beVerbose && collided)
+		warnx("Section hashmap collision occurred!");
+}
+
+struct Section *sect_GetSection(char const *name)
+{
+	return (struct Section *)hash_GetElement(sections, name);
+}
+
+void sect_CleanupSections(void)
+{
+	hash_EmptyMap(sections);
+}
+
+static void doSanityChecks(struct Section *section, void *ptr)
+{
+	(void)ptr;
+
+	/* Sanity check the section's type */
+
+	if (section->type < 0 || section->type >= SECTTYPE_INVALID)
+		errx(1, "Section \"%s\" has an invalid type.", section->name);
+	if (is32kMode && section->type == SECTTYPE_ROMX)
+		errx(1, "%s: ROMX sections cannot be used with option -t.",
+		     section->name);
+	if (isWRA0Mode && section->type == SECTTYPE_WRAMX)
+		errx(1, "%s: WRAMX sections cannot be used with options -w or -d.",
+		     section->name);
+	if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
+		errx(1, "%s: VRAM bank 1 can't be used with option -d.",
+		     section->name);
+
+	/*
+	 * Check if alignment is reasonable, this is important to avoid UB
+	 * An alignment of zero is equivalent to no alignment, basically
+	 */
+	if (section->isAlignFixed && section->alignMask == 1)
+		section->isAlignFixed = false;
+
+	uint32_t minbank = bankranges[section->type][0],
+		 maxbank = bankranges[section->type][1];
+
+	if (section->isBankFixed && section->bank < minbank
+				 && section->bank > maxbank)
+		errx(1, minbank == maxbank
+			? "Cannot place section \"%s\" in bank %d, it must be %d"
+			: "Cannot place section \"%s\" in bank %d, it must be between %d and %d",
+		     section->name, section->bank, minbank, maxbank);
+
+	/* Check if section has a chance to be placed */
+	if (section->size > maxsize[section->type])
+		errx(1, "Section \"%s\" is bigger than the max size for that type: %#X > %#X",
+		     section->size, maxsize[section->type]);
+
+	/* Translate loose constraints to strong ones when they're equivalent */
+
+	if (minbank == maxbank) {
+		section->bank = minbank;
+		section->isBankFixed = true;
+	}
+
+	if (section->isAlignFixed) {
+		enum SectionType type = section->type;
+
+		/* It doesn't make sense to have both org and alignment set */
+		if (section->isAddressFixed) {
+			if (section->org & section->alignMask)
+				errx(1, "Section \"%s\"'s fixed address doesn't match its alignment",
+				     section->name);
+			section->isAlignFixed = false;
+		} else if ((endaddr(type) & section->alignMask)
+			   == startaddr[type]) {
+			section->org = startaddr[type];
+			section->isAlignFixed = false;
+			section->isAddressFixed = true;
+		}
+	}
+}
+
+void sect_DoSanityChecks(void)
+{
+	sect_ForEach(doSanityChecks, NULL);
+}
--- a/src/link/symbol.c
+++ b/src/link/symbol.c
@@ -1,137 +1,56 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
 
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stdbool.h>
 
+#include "link/symbol.h"
+#include "link/main.h"
 #include "extern/err.h"
+#include "hashmap.h"
 
-#include "link/main.h"
-#include "link/patch.h"
-#include "link/mylink.h"
+HashMap symbols;
 
-#include "types.h"
-
-#define HASHSIZE 73
-
-struct ISymbol {
-	char *pzName;
-	int32_t nValue;
-	int32_t nBank; /* -1 = constant */
-	 /* Object file where the symbol was defined. */
-	char tzObjFileName[_MAX_PATH + 1];
-	/* Source file where the symbol was defined. */
-	char tzFileName[_MAX_PATH + 1];
-	/* Line where the symbol was defined. */
-	uint32_t nFileLine;
-	struct ISymbol *pNext;
+struct ForEachArg {
+	void (*callback)(struct Symbol *symbol, void *arg);
+	void *arg;
 };
 
-struct ISymbol *tHash[HASHSIZE];
-
-int32_t calchash(char *s)
+static void forEach(void *symbol, void *arg)
 {
-	int32_t r = 0;
+	struct ForEachArg *callbackArg = (struct ForEachArg *)arg;
 
-	while (*s)
-		r += *s++;
-
-	return r % HASHSIZE;
+	callbackArg->callback((struct Symbol *)symbol, callbackArg->arg);
 }
 
-void sym_Init(void)
+void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg)
 {
-	int32_t i;
+	struct ForEachArg callbackArg = { .callback = callback, .arg = arg};
 
-	for (i = 0; i < HASHSIZE; i += 1)
-		tHash[i] = NULL;
+	hash_ForEach(symbols, forEach, &callbackArg);
 }
 
-int32_t sym_GetValue(struct sPatch *pPatch, char *tzName)
+void sym_AddSymbol(struct Symbol *symbol)
 {
-	if (strcmp(tzName, "@") == 0)
-		return nPC;
+	/* Check if the symbol already exists */
+	struct Symbol *other = hash_GetElement(symbols, symbol->name);
 
-	struct ISymbol **ppSym;
+	if (other)
+		errx(1, "\"%s\" both in %s from %s(%d) and in %s from %s(%d)",
+		     symbol->name,
+		     symbol->objFileName, symbol->fileName, symbol->lineNo,
+		      other->objFileName,  other->fileName,  other->lineNo);
 
-	ppSym = &(tHash[calchash(tzName)]);
-	while (*ppSym) {
-		if (strcmp(tzName, (*ppSym)->pzName))
-			ppSym = &((*ppSym)->pNext);
-		else
-			return ((*ppSym)->nValue);
-	}
+	/* If not, add it */
+	bool collided = hash_AddElement(symbols, symbol->name, symbol);
 
-	errx(1,
-	     "%s(%ld) : Unknown symbol '%s'",
-	     pPatch->pzFilename, pPatch->nLineNo,
-	     tzName);
+	if (beVerbose && collided)
+		warnx("Symbol hashmap collision occurred!");
 }
 
-int32_t sym_GetBank(struct sPatch *pPatch, char *tzName)
+struct Symbol *sym_GetSymbol(char const *name)
 {
-	struct ISymbol **ppSym;
-
-	ppSym = &(tHash[calchash(tzName)]);
-	while (*ppSym) {
-		if (strcmp(tzName, (*ppSym)->pzName))
-			ppSym = &((*ppSym)->pNext);
-		else
-			return ((*ppSym)->nBank);
-	}
-
-	errx(1,
-	     "%s(%ld) : Unknown symbol '%s'",
-	     pPatch->pzFilename, pPatch->nLineNo,
-	     tzName);
+	return (struct Symbol *)hash_GetElement(symbols, name);
 }
 
-void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
-		      char *tzObjFileName, char *tzFileName, uint32_t nFileLine)
+void sym_CleanupSymbols(void)
 {
-	if (strcmp(tzName, "@") == 0)
-		return;
-
-	struct ISymbol **ppSym;
-
-	ppSym = &(tHash[calchash(tzName)]);
-
-	while (*ppSym) {
-		if (strcmp(tzName, (*ppSym)->pzName)) {
-			ppSym = &((*ppSym)->pNext);
-		} else {
-			if (nBank == -1)
-				return;
-
-			errx(1, "'%s' in both %s : %s(%d) and %s : %s(%d)",
-			     tzName, tzObjFileName, tzFileName, nFileLine,
-			     (*ppSym)->tzObjFileName,
-			     (*ppSym)->tzFileName, (*ppSym)->nFileLine);
-		}
-	}
-
-	*ppSym = malloc(sizeof **ppSym);
-
-	if (*ppSym != NULL) {
-		(*ppSym)->pzName = malloc(strlen(tzName) + 1);
-
-		if ((*ppSym)->pzName != NULL) {
-			strcpy((*ppSym)->pzName, tzName);
-			(*ppSym)->nValue = nValue;
-			(*ppSym)->nBank = nBank;
-			(*ppSym)->pNext = NULL;
-			strncpy((*ppSym)->tzObjFileName, tzObjFileName,
-				sizeof((*ppSym)->tzObjFileName));
-			strncpy((*ppSym)->tzFileName, tzFileName,
-				sizeof((*ppSym)->tzFileName));
-			(*ppSym)->nFileLine = nFileLine;
-		}
-	}
+	hash_EmptyMap(symbols);
 }
--- a/src/rgbds.5
+++ b/src/rgbds.5
@@ -91,8 +91,8 @@
                   ; decide (floating bank). This field is only valid for ROMX,
                   ; VRAM, WRAMX and SRAM sections.
 
-    LONG    Align ; Alignment of this section (expressed as number of low bits
-                  ; to leave as 0). -1 if not defined.
+    LONG    Align ; Alignment of this section, expressed as 1 << align. 1 if
+                  ; not specified.
 
     IF      (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.
 
--- a/test/link/romx-tiny-no-t.out
+++ b/test/link/romx-tiny-no-t.out
@@ -1,1 +1,1 @@
-error: Unable to place 'r0b' (ROM0 section) anywhere
+error: Unable to place "r0a" (ROM0 section) anywhere
--- a/test/link/romx-tiny-t.out
+++ b/test/link/romx-tiny-t.out
@@ -1,1 +1,1 @@
-error: ROMX sections can't be used with option -t.
+error: rx: ROMX sections cannot be used with option -t.
--- a/test/link/section-attributes-mismatch.link
+++ b/test/link/section-attributes-mismatch.link
@@ -1,5 +1,5 @@
 ROM0
-	org $10
+	org $18
 	"sec"
 	org $20
 	"secfix"
--- a/test/link/section-attributes-mismatch.out
+++ b/test/link/section-attributes-mismatch.out
@@ -1,2 +1,1 @@
-error: Different attributes for "sec" in source and linkerscript
-
+error: Linker script contradicts "sec"'s alignment
--- a/test/link/vram-fixed-dmg-mode-d.out
+++ b/test/link/vram-fixed-dmg-mode-d.out
@@ -1,1 +1,1 @@
-error: VRAM bank 1 can't be used with option -d.
+error: v1: VRAM bank 1 can't be used with option -d.
--- a/test/link/vram-floating-dmg-mode-d.out
+++ b/test/link/vram-floating-dmg-mode-d.out
@@ -1,1 +1,1 @@
-error: Unable to place 'v1' (VRAM section) in any bank
+error: Unable to place "v1" (VRAM section) anywhere
--- a/test/link/wramx-dmg-mode-d.out
+++ b/test/link/wramx-dmg-mode-d.out
@@ -1,1 +1,1 @@
-error: WRAMX sections can't be used with options -w or -d.
+error: wx: WRAMX sections cannot be used with options -w or -d.
--- a/test/link/wramx-dmg-mode-no-d.out
+++ b/test/link/wramx-dmg-mode-no-d.out
@@ -1,1 +1,1 @@
-error: Unable to place 'w0b' (WRAM0 section) anywhere
+error: Unable to place "w0b" (WRAM0 section) anywhere