Introduction

Some time ago I’ve written a simple gameboy emulator in go. After I knew this little machine a little better, I thought why not write a game for it. I’ve done some FlappyBird clone with gbdk and C but the game had very poor perfomance. Most people writing gameboy games simply use assembler which gives you much better control over the whole thing.

After that, I’ve designed a very simple language which was explicitly designed for the gameboy which should be able to generate better gameboy assembler code then a generic C compiler. A few times I’ve tried to create a compiler for that language but I had no real idea how to do so, and so I failed… :|

A few weeks ago I thought: Just try a bottom-up approach instead of top-down? Thats why I started looking at linkers and assemblers.

The Linker

My first plan was to only create the assembler and use the linker from RGBDS which documented the format for the object files. But with that information, writing a linker should be trivial.

That whole thing, with a fixed input (the object file) and a static transformation of that input sounded like a job for F#. First of all: I’m very new to F# and have barely used it, so forgive me my naive implementation.

The job of the linker is to take blocks of code or data from the assembler, arrange them, so they all fit into the rom file and patch jumps, calls and other stuff to match the actual addresses.

In general we will have the following input:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
type SymbolName = {
    Global: string       // the global part of the name
    Local: string option // global symbols might have multiple locals
}

type Symbol = {
    Name: SymbolName     // the name of the symbol
    Offset: uint16       // the offset, relative to the containing section.
    Exported: bool       // the symbol is also usable by other sections.
}

type BankNo = int // Some Areas have multiple banks, which might be switched

type Area = 
    | Rom of BankNo   // The actual rom
    | WRam  of BankNo // Internal RAM
    | VRam            // Video RAM (Sprite Data)
    | HRam            // High-RAM (Zero Page RAM) which can be accessed faster then normal RAM
    | SRam            // Optional additional RAM from the cartridge.
    | OAM             // Object Attribute Memory (Some kind of special video RAM)

type RomData = {
    Data: byte[]         // the actual data of the section. (only needed for rom sections.)
    Patches: Patch list  // Patches which are applied to the Data, once all sections are in place.
}

type Section = {
    Origin: uint16 option // a fixed location to put the section.
    Length: uint16        // the length of the section.
    Symbols: Symbol list  // Symbols which are provided by this section.
    Area: Area            // the area where this section should be located.
    Rom: RomData option   // Data and patches for any rom section.
}

type CartridgeHeader = {
    // We will have a look at this later on.
}

type Writer = System.IO.Stream -> unit
type LinkerResult = {
    Rom: Writer     // function to write the rom to a stream
    Symbols: Writer // function to write the symbol debug file to a stream.
}

let Link (header: CartridgeHeader) (sections: Section list) : Result<LinkerResult, string> = 
    Error "Not implemented yet :)"

Next time we will have a look at the CartridgeHeader of the gameboy and how this influences the linking process. Stay tuned.

Continue with Part 2