Build process information
NDS ROMs have an ARM9 binary, an ARM7 binary, an optional read-only filesystem, and a ROM header. The ARM9 and ARM7 binaries are loaded to RAM by the NDS ROM loader. The filesystem must be read at runtime, and it’s where you store additional graphics, audio, data (and potentially code) that doesn’t fit in your ARM9 and ARM7 binaries. The ROM header has information about where to locate all the parts of the ROM, as well as other information like the game title and icon. You can get more information about the header in GBATEK.
This document explains the way BlocksDS builds each component and assembles the final ROM.
The developer provides source code files that are compiled and linked into an
.elf file (one per CPU). The source code files may contain code or data as
requested by the developer. This code and data is placed into different sections
as with any other .elf file. The most important sections (which use standard
names) are:
text: Code.rodata: Constant data.data: Initialized variables.bss: Uninitialized variables (they start as 0).tdata,tbss: Thread-localdataandbss. Any variable with attribute__threadis thread-local, which means each thread has access to its own copy of the variable instead of all threads using the same variable.
Other interesting (but non-standard) sections are:
dtcm,sbss: DTCM is an ARM9 memory region that can be used for data (not for code) and which is very fast compared to main RAM. BlocksDS uses it for the ARM9 stack.sbssis a standardelfsection name kept for compatibility (for example, GBA linker scripts usesbssto refer to EWRAM).itcm,vectors: ITCM is an ARM9 memory region that can be used for code (and data) and that is also very fast compared to main RAM.vectorsrefers to the exception vectors.secure: This space is reserved for the cartridge secure area, which isn’t used by homebrew programs.
For more information about how BlocksDS uses the DS memory map check this document.
This elf file is very useful even after the ROM is built. It can be used with
binutils, for example, to analyse which functions are the biggest ones in your
program (with nm --size-sort file.elf). It can also be used to debug games in
emulators that support gdb, like melonDS and DeSmuMe.
The DSi complicates all of this a bit more. The DSi has 16 MiB of RAM, while a regular DS only has 4 MiB. It’s very useful for any DSi-compatible game to be able to not load DSi-related code and data when running on a regular DS. This is done by defining other sections that are ignored in a regular DS, and they are loaded to RAM in a DSi:
twl: Combinedtext,rodataanddatafor DSi.twl_bss: Uninitialized DSi-only variables.
The way the twl sections are used is explained below, in the section about
creation of NDS ROMs.
For more information, you can see the full linker scripts used by BlocksDS in this folder.
Note: In the past, the build process involved creating bin files from elf
files (with objcopy) and using them to build the final ROM. However, bin
files generated this way don’t contain information about the entrypoint address,
and it isn’t possible to determine which parts are only needed for DSi.
Building an ARM7/ARM9 binary for the NDS can be done with a generic toolchain
without any modifications, all you need to do is to provide a custom
linkerscript and crt0.
The ARM7 and ARM9 have harcoded memory layouts. Linkerscripts describe the memory map, and they are used by the linker in the linking step to assign addresses to all code and data in your program. As the ARM7 and ARM9 have slightly different memory maps, it is needed to have two different linkerscripts, one per CPU.
A crt0 file contains the code that runs right at the start of the program, and
this code:
-
Initializes the hardware to a sensible state. This involves configuring up the ARM9 MPU (Memory Protection Unit) so that DTCM and ITCM are available and the data and instruction caches are enabled. It also involves making sure that timers, DMA channels, audio, etc, are all stopped in case the loader didn’t do it.
-
Copies code and data sections to their final locations in physical memory.
-
Calls the
libcfunctions that initialize the C and C++ runtimes. -
Synchronizes the boot process of the ARM7 and ARM9 so that they are both ready before jumping to user code.
-
Jumps to
main().
It is possible to use BlocksDS without the default crt0 by passing
-nostartfiles to the linker, but then you will be in charge of initializing
the hardware yourself.
It is required to link libnds in all programs because of two reasons:
-
The
crt0expects some specific hardware initialization functions to exist (this code isn’t in thecrt0itself).libndscontains the implementation of the functions. -
libcandlibndsare tightly coupled. For example, you can usefopen()to open a file in the SD card of your flashcard, but that means thatlibcneeds to talk tolibndsto do the low-level filesystem handling.libcunderstands files, but it doesn’t understand filesystem formats, and it doesn’t know how to read an SD card.libndshas code to read SD cards, and it integratesFatFs, which understands the FAT filesystem format.
If you don’t use libnds you will need to write your own crt0, and either
give up on libc integration or implement it yourself.
In general, using libnds in all your programs isn’t a problem because its
license is Zlib, which doesn’t impose any restriction on how you distribute
your code or binaries, and it doesn’t require any attribution like copyright
notices in your game. You are very welcome to add an acknowledgment to its
authors, but it’s not mandatory.
Because of all the custom gcc options required to build NDS binaries
correctly, it is possible that new options are required after gcc updates, or
old options need to be removed, etc. Most of the options can be modified in the
makefile of a BlocksDS project. However, some of this is hidden from the user in
specs files located here.
This is done purely for end user convenience and to reduce the amount of
breaking changes to user project Makefiles. You can skip the specs files and
copy the build options and definitions to your makefile, but that will force you
to keep track of the changes in BlocksDS and to update your makefiles
accordingly.
The best way to understand how BlocksDS invokes gcc is to build a ROM that
uses one of the default makefiles (located
here).
If you build the binary with make V= instead of just make, the makefile will
print the exact commands used at every step of the build process. You will be
able to see which libraries are used, which compiler options are used, etc.
The size of the ARM7 and ARM9 binaries is limited to the available RAM of the
console. In order to support bigger games, the NDS ROM can contain a NitroFS
filesystem with your graphics, music, etc. That way you can have a smaller CPU
binary and load data from the filesystem whenever it’s required.
There is no standalone tool to generate a filesystem image. Developers are
expected to have one or more folders with data and pass them to ndstool when
the ROM is created. ndstool will generate a filesystem image by combining all
the folders provided by the user.
The DSi has another general-purpose CPU that can run user code, a Teak DSP. Binaries for this CPU are treated as generic data. You can add them to your CPU binaries, or keep them in the filesystem so that they can be switched at runtime. Note that this toolchain isn’t very mature, so you will need to be very careful and test your code often if you decide to use it.
ndstool is a program that can take the following and generate a NDS ROM out of
it:
- One ARM9
elffile. - One ARM7
elffile. If no file is specified,ndstoolwill use the default ARM7 of BlocksDS instead. - One or more folders to be added to the ROM filesystem. If no folder is provided, the filesystem will be kept emtpy.
- One ROM icon (which may be animated for DSi-compatible games). This icon is displayed in the NDS firmware menu. It’s optional.
ndstool reads the elf file of each CPU, takes every section in the elf
file, and saves it to the NDS ROM as binary data. This includes twl sections
as well as regular sections.
It is possible to generate a NDS ROM without any DSi code or data by running
ndstool with -h 0x200, which will force the NDS ROM header to be DS-only. In
this case, none of the the twl sections will be added to the final NDS ROM.
This option also generates a PassMe header; while initially intended for loading
DS homebrew from Slot-2 cartridges, it is also used by some outdated homebrew
application loaders.