shithub: rgbds

Download patch

ref: 0e24adcafdf2c4aab448cad21e4ce596b62d65eb
parent: 696feae32e87473811b59da174d7841a165ec42d
author: ISSOtm <[email protected]>
date: Thu Sep 19 10:27:37 EDT 2019

Rewrite RGBLINK entirely
The goal was to improve readability, but along the way a few things were
gained.
- Sorted sym and map files
- Infrastructure for supporting multiple .o versions
- Valgrind-proof, as far as my testing goes anyways
- Improved verbosity messages
- Added error checking
- Performance improvements, see end of commit message

The readability improvement was spurred while trying to make sense of the
old code while trying to implement features such as sorted sym and map
files.
I also did my best to remove hardcoded logic, such that modifications
should be doable; for example, "RAM loading" sections, which are linked
against a different location than the one they're stored at.

Some work remains to be done, see the "TODO:" and "FIXME:" comments.
Further, while regression tests pass, this new linker should be tested on
different codebases (ideally while instrumented with `make develop` and
under valgrind).
The few errors spotted in the man pages (alignment) need to be corrected.
Finally, documentation comments need to be written, I have written a lot of
them but not all.

This also provides a significant performance boost (benchmarked with a
51994-symbol project):

Current master RGBLINK:
2.02user 0.03system 0:02.06elapsed 99%CPU (0avgtext+0avgdata 84336maxresident)k
0inputs+11584outputs (0major+20729minor)pagefaults 0swaps

Rewritten RGBLINK:
0.19user 0.06system 0:00.63elapsed 40%CPU (0avgtext+0avgdata 32460maxresident)k
23784inputs+11576outputs (0major+7672minor)pagefaults 0swaps

--- 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,35 @@
 /*
  * 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 FILE *linkerScript;
+extern FILE *mapFile;
+extern FILE *symFile;
+extern FILE *overlayFile;
+extern FILE *outputFile;
+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)
 
 #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,21 @@
 /*
  * 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"
+
+void out_AddSection(struct Section const *section);
+
+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,34 @@
 /*
  * 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"
+struct SectionPlacement {
+	struct Section *section;
+	uint16_t org;
+	uint32_t bank;
+};
 
-noreturn_ void script_fatalerror(const char *fmt, ...);
+extern uint64_t script_lineNo;
 
-void script_Parse(const char *path);
+/**
+ * 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_IncludeFile(const char *path);
-int32_t script_IncludeDepthGet(void);
-void script_IncludePop(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;
--- /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,401 @@
-/*
- * 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 (!linkerScript)
+		return;
+	verbosePrint("Reading linker script...\n");
 
-	if ((org >= pArea->nOrg)
-	    && ((org + size) <= (pArea->nOrg + pArea->nSize))) {
+	/* Modify all sections according to the linker script */
+	struct SectionPlacement *placement;
 
-		if (org == pArea->nOrg) {
-			pArea->nOrg += size;
-			pArea->nSize -= size;
-			return org;
-		}
+	while ((placement = script_NextSection())) {
+		struct Section *section = placement->section;
 
-		if ((org + size) == (pArea->nOrg + pArea->nSize)) {
-			pArea->nSize -= size;
-			return org;
-		}
+		/* 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);
 
-		struct sFreeArea *pNewArea;
-
-		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;
 }
 
-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)
+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)
+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)
+					/* Try again in next bank */
+					space = NULL;
+				else
+					location->address = section->org;
+			} 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;
-}
-
-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;
 	}
-
-	return 0;
 }
 
-struct sSection *GetSectionByName(const char *name)
+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)
+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
-	 */
+	nbSectionsToAssign = 0;
+	sect_ForEach(categorizeSection, NULL);
 
-	for (int32_t i = 0; i < BANK_INDEX_MAX; i += 1) {
-		BankFree[i] = malloc(sizeof(*BankFree[i]));
+	initFreeSpace();
 
-		if (!BankFree[i]) {
-			errx(1, "%s: Couldn't allocate mem for bank %d",
-			     __func__, i);
-		}
+	/* Process linker script, if any */
+	processLinkerScript();
 
-		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 (overlayFile)
+		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,177 @@
 /*
  * 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 <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 */
+FILE *linkerScript; /* -l */
+FILE *mapFile;      /* -m */
+FILE *symFile;      /* -n */
+FILE *overlayFile;  /* -O */
+FILE *outputFile;   /* -o */
+uint8_t padValue;   /* -p */
+bool is32kMode;     /* -t */
+bool beVerbose;     /* -v */
+bool isWRA0Mode;    /* -w */
 
-int32_t options;
-int32_t fillchar;
-char *smartlinkstartsymbol;
-
-/*
- * Print the usagescreen
+/**
+ * Prints the program's usage to stdout.
  */
+static void printUsage(void)
+{
+	puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
+	     "               [-o outfile] [-p pad_value] [-s symbol] file [...]");
+}
 
-static void print_usage(void)
+/**
+ * Helper function for `main`'s argument parsing.
+ * For use with options which take a file name to operate on
+ * If the file fails to be opened, an error message will be printed,
+ * and the function `exit`s.
+ * @param mode The mode to open the file in
+ * @param failureMessage A format string that will be printed on failure.
+ *                       A single (string) argument is given, the file name.
+ * @return What `fopen` returned; this cannot be NULL.
+ */
+static FILE *openArgFile(char const *mode, char const *failureMessage)
 {
-	printf(
-"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
-"               [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
-	exit(1);
+	FILE *file = fopen(optarg, mode);
+
+	if (!file)
+		err(1, failureMessage, optarg);
+	return 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)
+{
+	if (linkerScript)
+		fclose(linkerScript);
+	if (mapFile)
+		fclose(mapFile);
+	if (symFile)
+		fclose(symFile);
+	if (overlayFile)
+		fclose(overlayFile);
+	if (outputFile)
+		fclose(outputFile);
 
+	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);
+			linkerScript = openArgFile("r", "Could not open linker script file \"%s\"");
 			break;
 		case 'm':
-			SetMapfileName(optarg);
+			mapFile = openArgFile("w", "Could not open map file \"%s\"");
 			break;
 		case 'n':
-			SetSymfileName(optarg);
+			symFile = openArgFile("w", "Could not open sym file \"%s\"");
 			break;
-		case 'o':
-			out_Setname(optarg);
-			break;
 		case 'O':
-			out_SetOverlayname(optarg);
-			options |= OPT_OVERLAY;
+			overlayFile = openArgFile("r+b", "Could not open overlay file \"%s\"");
 			break;
+		case 'o':
+			outputFile = openArgFile("wb", "Could not open output file \"%s\"");
+			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,461 @@
-/*
- * 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)
-{
-	uint32_t r;
+#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)
 
-	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;
-
-	return (int32_t)r;
-}
-
-/*
- * Read a NULL terminated string from a file
+/**
+ * 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.
  */
-int32_t readasciiz(char **dest, FILE *f)
+static int64_t readlong(FILE *file)
 {
-	size_t r = 0;
+	uint32_t value = 0;
 
-	size_t bufferLength = 16;
-	char *start = malloc(bufferLength);
-	char *s = start;
+	/* Read the little-endian value byte by byte */
+	for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
+		int byte = getc(file);
 
-	if (!s)
-		err(1, "%s: Couldn't allocate memory", __func__);
-
-	while (((*s++) = fgetc(f)) != 0) {
-		r += 1;
-
-		if (r >= bufferLength) {
-			bufferLength *= 2;
-			start = realloc(start, bufferLength);
-			if (!start) {
-				err(1, "%s: Couldn't allocate memory",
-				    __func__);
-			}
-			s = start + r;
-		}
+		if (byte == EOF)
+			return INT64_MAX;
+		value |= (uint8_t)byte << shift;
 	}
-
-	*dest = start;
-	return (r + 1);
+	return value;
 }
 
-/*
- * Allocate a new section and link it into the list
+/**
+ * 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
  */
-struct sSection *AllocSection(void)
-{
-	struct sSection **ppSections;
+#define tryReadlong(var, file, ...) \
+	tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
 
-	if (oReadLib == 1)
-		ppSections = &pLibSections;
-	else
-		ppSections = &pSections;
+/* There is no `readbyte`, just use `fgetc` or `getc`. */
 
-	while (*ppSections)
-		ppSections = &((*ppSections)->pNext);
+/**
+ * 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__)
 
-	*ppSections = malloc(sizeof **ppSections);
-	if (!*ppSections)
-		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 tryGetc(var, file, ...) \
+	tryRead(getc, int, EOF, var, file, __VA_ARGS__)
 
-	(*ppSections)->tSymbols = tSymbols;
-	(*ppSections)->pNext = NULL;
-	(*ppSections)->pPatches = NULL;
-	(*ppSections)->oAssigned = 0;
-	return *ppSections;
-}
-
-/*
- * Read a symbol from a file
+/**
+ * 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 sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
+static char *readstr(FILE *file)
 {
-	struct sSymbol *pSym;
+	/* 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;
 
-	pSym = malloc(sizeof(*pSym));
-	if (!pSym)
-		err(1, "%s: Couldn't allocate memory", __func__);
+	do {
+		/* Prepare going to next char */
+		index++;
+		/* 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;
+		}
+		/* Read char */
+		str[index] = getc(file);
+	} while (str[index]);
+	return str;
+}
 
-	readasciiz(&pSym->pzName, f);
-	pSym->Type = (enum eSymbolType)fgetc(f);
+/**
+ * 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
+ */
+#define tryReadstr(var, file, ...) \
+	tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
 
-	pSym->pzObjFileName = tzObjectfile;
+/***** Functions to parse object files *****/
 
-	if (pSym->Type != SYM_IMPORT) {
-		readasciiz(&pSym->pzFileName, f);
-		pSym->nFileLine = readlong(f);
-
-		pSym->nSectionID = readlong(f);
-		pSym->nOffset = readlong(f);
+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;
 	}
-
-	return pSym;
 }
 
-/*
- * RGB object reader routines
- */
-struct sSection *obj_ReadRGBSection(FILE *f)
+static void readPatch(FILE *file, struct Patch *patch,
+		      char const *fileName, char const *sectName, uint32_t i)
 {
-	struct sSection *pSection;
-	char *pzName;
+	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);
 
-	readasciiz(&pzName, f);
-	if (IsSectionNameInUse(pzName))
-		errx(1, "Section name \"%s\" is already in use.", pzName);
+	uint8_t *rpnExpression =
+		malloc(sizeof(*rpnExpression) * patch->rpnSize);
+	size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression),
+				      patch->rpnSize, file);
 
-	pSection = AllocSection();
-	pSection->pzName = pzName;
+	if (nbElementsRead != patch->rpnSize)
+		errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s",
+		     fileName, sectName, i);
+	patch->rpnExpression = rpnExpression;
+}
 
-	pSection->nByteSize = readlong(f);
-	pSection->Type = (enum eSectionType)fgetc(f);
-	pSection->nOrg = readlong(f);
-	pSection->nBank = readlong(f);
-	pSection->nAlign = readlong(f);
+static void readSection(FILE *file, struct Section *section,
+			char const *fileName)
+{
+	int32_t tmp;
 
-	if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX))
-		errx(1,  "ROMX sections can't be used with option -t.");
+	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;
 
-	if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX))
-		errx(1, "WRAMX sections can't be used with options -w or -d.");
+	if (sect_HasData(section->type)) {
+		/* Ensure we never allocate 0 bytes */
+		uint8_t *data = malloc(sizeof(*data) * section->size + 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 (!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;
 
-	uint32_t maxsize = 0;
+		tryReadlong(section->nbPatches, file,
+			    "%s: Cannot read \"%s\"'s number of patches: %s",
+			    fileName, section->name);
 
-	/* 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);
-	}
+		struct Patch *patches =
+			malloc(sizeof(*patches) * section->nbPatches + 1);
 
-	/*
-	 * 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 (!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;
 	}
+}
 
-	pSection->pData = malloc(pSection->nByteSize);
-	if (!pSection->pData)
-		err(1, "%s: Couldn't allocate memory", __func__);
+static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
+{
+	uint32_t a = 0, b = section->nbSymbols;
 
-	int32_t nNumberOfPatches;
-	struct sPatch **ppPatch, *pPatch;
+	while (a != b) {
+		uint32_t c = (a + b) / 2;
 
-	if (fread(pSection->pData, sizeof(uint8_t), pSection->nByteSize, f)
-	    != pSection->nByteSize) {
-		err(1, "%s: Read error", __func__);
+		if (section->symbols[c]->offset > symbol->offset)
+			b = c;
+		else
+			a = c + 1;
 	}
 
-	nNumberOfPatches = readlong(f);
-	ppPatch = &pSection->pPatches;
+	struct Symbol const *tmp = symbol;
 
-	/*
-	 * And patches...
-	 */
-	while (nNumberOfPatches--) {
-		pPatch = malloc(sizeof(*pPatch));
-		if (!pPatch)
-			err(1, "%s: Couldn't allocate memory", __func__);
+	for (uint32_t i = a; i <= section->nbSymbols; i++) {
+		symbol = tmp;
+		tmp = section->symbols[i];
+		section->symbols[i] = symbol;
+	}
 
-		*ppPatch = pPatch;
-		readasciiz(&pPatch->pzFilename, f);
-		pPatch->nLineNo = readlong(f);
-		pPatch->nOffset = readlong(f);
-		pPatch->Type = (enum ePatchType)fgetc(f);
-		pPatch->nRPNSize = readlong(f);
+	section->nbSymbols++;
+}
 
-		if (pPatch->nRPNSize > 0) {
-			pPatch->pRPN = malloc(pPatch->nRPNSize);
-			if (!pPatch->pRPN) {
-				err(1, "%s: Couldn't allocate memory",
-				    __func__);
-			}
+static void readRGB6File(FILE *file, char const *fileName)
+{
+	uint32_t nbSymbols;
+	uint32_t nbSections;
 
-			if (fread(pPatch->pRPN, sizeof(uint8_t),
-				  pPatch->nRPNSize, f) != pPatch->nRPNSize) {
-				errx(1, "%s: Read error", __func__);
-			}
-		} else {
-			pPatch->pRPN = NULL;
-		}
+	tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
+		    fileName);
+	tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
+		    fileName);
 
-		pPatch->pNext = NULL;
-		ppPatch = &(pPatch->pNext);
-	}
+	nbSectionsToAssign += nbSections;
 
-	return pSection;
-}
+	/* This file's symbols, kept to link sections to them */
+	struct Symbol **fileSymbols =
+		malloc(sizeof(*fileSymbols) * nbSymbols + 1);
 
-void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
-{
-	struct sSection *pFirstSection;
-	int32_t nNumberOfSymbols, nNumberOfSections, i;
+	if (!fileSymbols)
+		err(1, "Failed to get memory for %s's symbols", fileName);
 
-	nNumberOfSymbols = readlong(pObjfile);
-	nNumberOfSections = readlong(pObjfile);
+	struct SymbolList *symbolList = malloc(sizeof(*symbolList));
 
-	/* First comes the symbols */
+	if (!symbolList)
+		err(1, "Failed to register %s's symbol list", fileName);
+	symbolList->symbolList = fileSymbols;
+	symbolList->nbSymbols = nbSymbols;
+	symbolList->next = symbolLists;
+	symbolLists = symbolList;
 
-	if (nNumberOfSymbols) {
-		tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
-		if (!tSymbols)
-			err(1, "%s: Couldn't allocate memory", __func__);
+	uint32_t nbSymPerSect[nbSections];
 
-		for (i = 0; i < nNumberOfSymbols; i += 1)
-			tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
-	} else {
-		tSymbols = NULL;
-	}
+	memset(nbSymPerSect, 0, sizeof(nbSymPerSect));
 
-	/* Next we have the sections */
+	verbosePrint("Reading %u symbols...\n", nbSymbols);
+	for (uint32_t i = 0; i < nbSymbols; i++) {
+		/* Read symbol */
+		struct Symbol *symbol = malloc(sizeof(*symbol));
 
-	pFirstSection = NULL;
-	while (nNumberOfSections--) {
-		struct sSection *pNewSection;
+		if (!symbol)
+			err(1, "%s: Couldn't create new symbol", fileName);
+		readSymbol(file, symbol, fileName);
 
-		pNewSection = obj_ReadRGBSection(pObjfile);
-		pNewSection->nNumberOfSymbols = nNumberOfSymbols;
-		if (pFirstSection == NULL)
-			pFirstSection = pNewSection;
+		fileSymbols[i] = symbol;
+		if (symbol->type == SYMTYPE_EXPORT)
+			sym_AddSymbol(symbol);
+		if (symbol->sectionID != -1)
+			nbSymPerSect[symbol->sectionID]++;
 	}
 
-	/*
-	 * Fill in the pSection entry in the symbolstructure.
-	 * This REALLY needs some cleaning up... but, hey, it works
-	 */
+	/* This file's sections, stored in a table to link symbols to them */
+	struct Section *fileSections[nbSections ? nbSections : 1];
 
-	for (i = 0; i < nNumberOfSymbols; i += 1) {
-		struct sSection *pConvSect = pFirstSection;
+	verbosePrint("Reading %u sections...\n", nbSections);
+	for (uint32_t i = 0; i < nbSections; i++) {
+		/* Read section */
+		struct Section *section = malloc(sizeof(*section));
 
-		if ((tSymbols[i]->Type != SYM_IMPORT) &&
-		    (tSymbols[i]->nSectionID != -1)) {
-			int32_t j = 0;
+		if (!section)
+			err(1, "%s: Couldn't create new section", fileName);
+		readSection(file, section, fileName);
+		section->fileSymbols = fileSymbols;
 
-			while (j != tSymbols[i]->nSectionID) {
-				j += 1;
-				pConvSect = pConvSect->pNext;
-			}
-			tSymbols[i]->pSection = pConvSect;
+		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 {
-			tSymbols[i]->pSection = NULL;
+			section->symbols = NULL;
 		}
+		section->nbSymbols = 0;
 	}
+
+	/* Give symbols pointers to their sections */
+	for (uint32_t i = 0; i < nbSymbols; i++) {
+		int32_t sectionID = fileSymbols[i]->sectionID;
+
+		if (sectionID == -1) {
+			fileSymbols[i]->section = NULL;
+		} else {
+			fileSymbols[i]->section = fileSections[sectionID];
+			/* Give the section a pointer to the symbol as well */
+			linkSymToSect(fileSymbols[i], fileSections[sectionID]);
+		}
+	}
 }
 
-/*
- * The main objectfileloadroutine (phew)
- */
-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,344 @@
-/*
- * 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 "extern/err.h"
-
-#include "link/mylink.h"
-#include "link/mapfile.h"
+#include "link/output.h"
 #include "link/main.h"
-#include "link/assign.h"
+#include "link/section.h"
+#include "link/symbol.h"
 
-char *tzOutname;
-char *tzOverlayname;
+#include "extern/err.h"
 
-int32_t MaxOverlayBank;
+struct SortedSection {
+	struct Section const *section;
+	struct SortedSection *next;
+};
 
-void writehome(FILE *f, FILE *f_overlay)
+static struct {
+	uint32_t nbBanks;
+	struct SortedSections {
+		struct SortedSection *sections;
+		struct SortedSection *zeroLenSections;
+	} *banks;
+} sections[SECTTYPE_INVALID];
+
+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)
+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");
+
+	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);
+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)
+static void writeROM(void)
 {
-	tzOutname = tzOutputfile;
+	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);
+	}
 }
 
-void out_SetOverlayname(char *tzOverlayfile)
+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)
+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.sect) {
+			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);
+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;
 
-	/*
-	 * Write ROM.
-	 */
+		slack -= sect->size;
 
-	f = fopen(tzOutname, "wb");
-	if (f != NULL) {
-		writehome(f, f_overlay);
-		for (i = 1; i <= MaxBankUsed; i += 1)
-			writebank(f, f_overlay, i);
+		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);
 
-		fclose(f);
+		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;
 	}
 
-	/*
-	 * Close overlay
-	 */
+	if (slack == maxsize[type])
+		fputs("  EMPTY\n\n", mapFile);
+	else
+		fprintf(mapFile, "    SLACK: $%04x byte%s\n\n", slack,
+			slack == 1 ? "" : "s");
+}
 
-	if (tzOverlayname)
-		fclose(f_overlay);
+static void writeSymAndMap(void)
+{
+	if (!symFile && !mapFile)
+		return;
 
-	/*
-	 * Add regular sections to map and sym files.
-	 */
+	enum SectionType typeMap[SECTTYPE_INVALID] = {
+		SECTTYPE_ROM0,
+		SECTTYPE_ROMX,
+		SECTTYPE_VRAM,
+		SECTTYPE_SRAM,
+		SECTTYPE_WRAM0,
+		SECTTYPE_WRAMX,
+		SECTTYPE_OAM,
+		SECTTYPE_HRAM
+	};
 
-	for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
-		const struct sSection *pSect;
+	if (symFile)
+		fputs("; File generated by rgblink\n", symFile);
 
-		MapfileInitBank(i);
-		pSect = pSections;
-		while (pSect) {
-			if (pSect->nBank == i)
-				MapfileWriteSection(pSect);
-			pSect = pSect->pNext;
+	for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
+		enum SectionType type = typeMap[i];
+
+		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);
+			}
 		}
-		MapfileCloseBank(area_Avail(i));
 	}
+}
+
+static void cleanupSections(struct SortedSection *section)
+{
+	while (section) {
+		struct SortedSection *next = section->next;
+
+		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);
+		}
+	}
+}
+
+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,328 @@
-/*
- * 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)
+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) {
+				symbol = sym_GetSymbol(symbol->name);
+				if (!symbol)
+					errx(1, "%s(%d): Unknown symbol \"%s\"",
+					     patch->fileName, patch->lineNo,
+					     symbol->name);
 			}
+
+			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) {
+				symbol = sym_GetSymbol(symbol->name);
+				if (!symbol)
+					errx(1, "%s(%d): Unknown symbol \"%s\"",
+					     patch->fileName, patch->lineNo,
+					     symbol->name);
+			}
 
-			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)
+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,410 @@
-/*
- * 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"
+static inline bool isWhiteSpace(int c)
+{
+	return c == ' ' || c == '\t';
+}
 
-static struct {
-	uint32_t address; /* current address to write sections to */
-	uint32_t top_address; /* not inclusive */
-	enum eSectionType type;
-} bank[BANK_INDEX_MAX];
+static inline bool isNewline(int c)
+{
+	return c == '\r' || c == '\n';
+}
 
-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 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;
 
-/* Current section attributes */
-static int32_t fix_org = -1;
-static int32_t fix_align = 1;
+	if (*str == '$') {
+		str++;
+		base = 16;
+	}
 
-void script_InitSections(void)
-{
-	int32_t i;
+	/* An empty string is not a number */
+	if (!*str)
+		return false;
 
-	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);
+	*number = 0;
+	do {
+		char chr = toupper(*str++);
+		uint8_t digit = 0;
+
+		while (digit < base) {
+			if (chr == digits[digit])
+				break;
+			digit++;
 		}
-	}
+		if (digit == base)
+			return false;
+		*number = *number * base + digit;
+	} while (*str);
+
+	return true;
 }
 
-void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
+enum LinkerScriptTokenType {
+	TOKEN_NEWLINE,
+	TOKEN_COMMAND,
+	TOKEN_BANK,
+	TOKEN_NUMBER,
+	TOKEN_SECTION,
+	TOKEN_EOF,
+
+	TOKEN_INVALID
+};
+
+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 uint32_t lineNo;
+
+static int readChar(FILE *file)
 {
-	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);
+	int curchar = getc_unlocked(file);
+
+	if (curchar == EOF && ferror(file))
+		err(1, "%s: Unexpected error reading linker script", __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_SECTION)
+		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 section name */
+		token.type = TOKEN_SECTION;
+		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, "Line %u: Unterminated string", 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 section name",
+					    __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_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);
+
+		token.type = TOKEN_INVALID;
+		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_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");
 
-		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);
+		if (token.type == TOKEN_INVALID) {
+			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_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);
+
+		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, "Unknown token \"%s\" on linker script line %u",
+				     str, lineNo);
 		}
-		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__);
-		}
-		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__);
-		}
-		current_bank = BANK_INDEX_HRAM;
-		current_real_bank = 0;
-		return;
+
+		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, "Linkerscript line %u: `%s` cannot be used to go backwards",
+		     lineNo, commands[command]);
+	*pc = arg;
 }
 
-void script_SetAlignment(uint32_t alignment)
+enum LinkerScriptParserState {
+	PARSER_FIRSTTIME,
+	PARSER_LINESTART,
+	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, "Linkerscript line %u: PC overflowed (%u > %u)",
+				     lineNo, curaddr[type][bankID],
+				     endaddr(type));
+			if (curaddr[type][bankID] < startaddr[type])
+				errx(1, "Linkerscript line %u: PC underflowed (%u < %u)",
+				     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:
+				return NULL;
+
+			case TOKEN_NUMBER:
+				errx(1, "Linkerscript line %u: stray number",
+				     lineNo);
+
+			case TOKEN_NEWLINE:
+				lineNo++;
+				break;
+
+			case TOKEN_SECTION:
+				parserState = PARSER_LINEEND;
+
+				if (type == SECTTYPE_INVALID)
+					errx(1, "Linkerscript line %u: Didn't specify a location before the section",
+					     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, "Linkerscript line %u: Didn't specify a location before the command",
+						     lineNo);
+					if (!hasArg)
+						errx(1, "Linkerscript line %u: Command specified without an argument",
+						     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, "Linkerscript line %u: Didn't specify a bank number",
+						     lineNo);
+					else if (!hasArg)
+						arg = bankranges[type][0];
+					else if (arg < bankranges[type][0])
+						errx(1, "Linkerscript line %u: specified bank number is too low (%u < %u)",
+						     lineNo, arg,
+						     bankranges[type][0]);
+					else if (arg > bankranges[type][1])
+						errx(1, "Linkerscript line %u: specified bank number is too high (%u > %u)",
+						     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;
+			}
+			break;
+
+		case PARSER_LINEEND:
+lineend:
+			if (token->type == TOKEN_EOF)
+				return NULL;
+			else if (token->type != TOKEN_NEWLINE)
+				errx(1, "Linkerscript line %u: Unexpected token at the end",
+				     lineNo);
+			lineNo++;
+			parserState = PARSER_LINESTART;
+			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/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