Introduction to Nintendo DS Programming

Revision History
Revision 6.003 Jan 2008jna
This manual now covers additional aspects of Nintendo DS Programming including multiple sprites, multiple backgrounds, multiple palettes, the touch screen, Slot-1 devices, and more. The manual's case study has undergone a significant rewrite and covers more ground than ever before.
Revision 5.031 Mar 2007jna
Made Editing improvements. Manual is now DocBook formatted.
Revision 4.031 Oct 2006jna
Added VRAM Appendix, updated page layout, fixed even more typos.
Revision 3.023 Sep 2006jna
Added a sound chapter
Revision 2.212 July 2006jna
Various typo fixes
Revision 2.113 May 2006jna
Fixed some code, spelling, and stupid little extra words.
Revision 2.121 Mar 2006jna
Finalized many chapters and improved code.

Table of Contents

Preface
The Main Issue
The Solution
How to Use this Manual
1. Politics of the Nintendo DS Homebrew Movement
Background Information
Is Homebrew Legal?
2. What is a passthrough device and how do I use one?
Purpose of the Passthrough
How a PassMe Works
History of the Passthrough
The Future is Now, and Now, and Now, and Now
About the NoPass
About Slot-1 Devices
How do I get a Passthrough
Which Passthrough Should I Buy?
How would I choose an old style passthrough?
PassMe 2 Buying Tips
How do I use my Passthrough
What to do with your Passthrough
3. How do I get programs into my Nintendo DS?
The Methods
Which Slot-1 Device should I buy?
What is DLDI?
So, which Slot-1 devices are good?
R4DS
M3 Real
Cyclo DS Evolution
Where do I get one of these Slot-1 devices?
The Slot-2 Device of Choice
Running Multiple Software Titles
4. How do I create programs?
All About devkitPro
The Wonderful World of libnds
Installing devkitARM
Installing libnds from source
The Next Step
5. How do I display a background?
Some Background Information
The 2D Graphics Engines
The Fifth Mode
A Fine Affine Background
Coding with this Manual
Initializing the Hardware
Configuring the VRAM Banks
Setting up the Affine Backgrounds
Fixed Point Number Primer
The Basics of DMA
Working with the Makefile
Gritty Crash Course
Putting in the Star Fields
Compiling
6. What is a sprite? How do I use them?
Magical Fairies?
The OAM
Information About the Sprite Hardware
How Sprites are Stored in Memory
Sprite Attributes
Updating the OAM
Initializing the OAM
Rotating Sprites
Showing and Hiding Sprites
Moving Sprites
Setting Sprite Priorities
Using the Sprites
Setting up VRAM for Sprites
Sprite Tile Addressing
Loading in a Sprite
What are assertions?
Displaying the Sprites
Compiling
7. Basic Game Mechanics Applied to the Space Shooter Genre
The Importance of Object Oriented Programming
The Ship Class
Making the Ship Class
The Constructor
Acceleration
Moving the Ship
Reversing the Ship's Direction
Rotating the Ship
Getting the Ship's Position
Getting the Ship's Angle
Linking the Ship into our Program
Creating the Main Game Loop
Compiling
8. Nintendo DS Input Systems
Overview
Key Input
Touch!
Writing an Input Updating Function
Writing an Input Handling Function
Creating the Main Game Loop, Again
Compiling
9. What about the sounds?
A Sound Theory
The Hardware
Making the Sounds
Using the Sounds
Getting Down with the Code
Wait, What's Going on Here?
The Need for Inter-processor Communication
Compiling

List of Figures

2.1. Picture of an early PassMe (left) and an FPGA (right)
2.2. The PassMe inserted into the DS card slot
3.1. Comparison of GBAMP (left) and a GBA flash cart (right) inserted into a DS
5.1. The Raster Display
5.2. libnds Affine Background API
5.3. Integer variables can be used to represent fractions.
5.4. The program should look like this when run.
6.1. The upper text shows information as it would be on a non-tiled background. The lower text shows the same data, tiled, for use in tiled graphic modes.
6.2. Output with both backgrounds and a sprite.
8.1. Flying around in the Orange Shuttle.
9.1. Flying around in the Orange Shuttle, with sound!

List of Tables

2.1. When ejecting the game, you'll discover your firmware version as shown.
5.1. Mode 5 Information
5.2. VRAM Bank Information
7.1. Table of Ship properties and functionality.
8.1. libnds Key Defines

Since the Nintendo DS debut, Nintendo enthusiasts ranging from pre-pubescent kids to 30-year-old college dropouts have been wanting to develop their own games and applications for the Nintendo DS. Nintendo has stated that the DS stands for "Developer's System". For those worthy enough to land a nice developing contract with Nintendo, it truly is. However, most people will never receive this contract, special permission from Nintendo to commercially produce games for the Nintendo DS. In order to obtain a contract with Nintendo, you must prove your worthiness by showcasing an amazing game or other piece of software to them. You must have a stable financial history and expected stable financial future. You must have ample funding to buy all the official Nintendo equipment to develop for the system. Most game development houses don't even get that far. Most games on the market today are put out by what is referred to as a publisher. Game development houses will produce their game partially, show it to a publisher, and the publisher (who already has this development contract with Nintendo) will fund the game development house and market their game. All this bureaucracy makes it very difficult for the common person to produce their own, personal-use games.

This is where the homebrew movement comes in. Dedicated hobbyists spend weeks reverse engineering the Nintendo DS hardware, just to make it possible for common people to produce for the system (by providing a cheap alternative to official Nintendo development). These dedicated hobbyists come from all walks of life and cultures, many from Europe and the U.S., and bring with them great knowledge. These people make up the homebrew movement.

Homebrew is legal for a number of reasons. You own the hardware you reverse engineer, so you are free to do with it as you will so long as you don't break the law. Breaking the law would include breaking proprietary copy protection, pirating video games, publishing illegally obtained trade secrets, or otherwise trying to profit off someone else's hard work. Homebrew poses no threat to the official developer kit, as it is so primitive in comparison. Even if you made something to compete with officially produced software, it would be near impossible to publish it. Companies often benefit from homebrew communities. Although software pirates often steal from homebrew discoveries to pirate software, the homebrew community abhors piracy and takes a strong stance against it.

When you buy a piece of hardware, you own it. This means that you are free to break it open, dive into it, reverse engineer it, and so forth. You may void your warranty, but thats the price for learning the intimacies of any system. The only illegal things on this line would be to put into production and sell products made with patented features (without negotiating a production deal with he patent owner), bypassing or breaking copy-protection, or stealing software code. Reverse engineering to learn about how the hardware works and to make something fun for the community is totally fine.

The homebrew tools available for game programming are far behind anything the game company who produced the system could provide (the official development kits). Game system developers have an intimate knowledge of the hardware, as they developed it. The homebrew community has only outsider knowledge through experimentation with the hardware.

It would be close to impossible to publish a game made with homebrew tools. Nintendo would not license your game. It would be hard to find another publisher who would try to publish something made with homebrew tools against Nintendo's will. On other systems besides the Nintendo DS, this is also true.

Companies often don't have a problem with homebrew because it increases the demand for their gaming systems and helps them to learn more about their consumer base. One example of this is with the Xbox. The Xbox homebrew community made the Xbox do things that Microsoft never thought consumers wanted like playindie games, emulate classic game systems, run the Linux operating system, and so forth. Microsoft then included a lot of these features (excepting Linux, of course) in their most recent gaming console, the Xbox 360 via a system called XNA Game Sudio and Xbox LIVE Arcade (XBLA). If a company wants to squash homebrew developers for whatever reason, they'll be smashing an essential fan base that loves that company's hardware design and has the potential to improve it (all at no cost to the company). Homebrew caused such a high demand for the Xbox that it would not have been in Microsoft's best interests to ignore or punish it.

The downside of homebrew is that software pirates often steal from the discoveries of homebrew and use that information to bypass copy-protection and to pirate games. Some companies may take a stance against homebrew for this reason, but doing so is unproductive. Piracy is regrettably inevitable in any industry. It is extremely destructive, annihilating game development houses because publishers will no longer publish their games due to a high piracy rating on the platform the game developers are developing for. Homebrew developers know this, and as the amateur brothers of the official game developers, they share the pain. Homebrew will usually keep all information regarding copy-protection in high secrecy; even if they know how to copy games, they will not share the information. The homebrew community does not want to see the the system they so dearly love come to an early death.

If you still want to buy an old fashioned style passthrough, there are a number of things you'll need to consider. Depending on what kind of Nintendo DS you have, you can buy either of two different types of passthrough devices. The first one, akin to the original made by DarkFader, is the "PassMe". The PassMe will probably work on most early "phat" (non-Nintendo DS Lite) Nintendo DS systems.

However, if your Nintendo DS is newer (both phat and Lite systems can be new), you will most likely need a "PassMe 2". The need for a PassMe 2 came about because of a change in the firmware of newer Nintendo DS systems. These newer systems do not allow the header to be changed to point to code in the GBA slot. However, it is possible to point to code in the GBA carts SRAM still. The PassMe 2 points to some certain instructions that have been loaded into memory by the currently inserted DS game. This location is an SWI (software interrupt) call to the SRAM on the GBA port. The GBA carts SRAM contains the necessary code to jump to code lo- cated on GBA cart. The DS then is told to run this code and thus redirects to code in the GBA slot. Each PassMe 2 has to be programmed to work with one specific game, as the certain code to redirect to the GBA slot is found in different places within different DS games.

There are a few tricks you can pull to determine your firmware version. It involves pictochat and pulling a cartridge out of your Nintendo DS. The screen will change a certain color. Based on this color you can determine if you need a PassMe 2 or not. Table 2.1, “When ejecting the game, you'll discover your firmware version as shown.” will help you discover which firmware version you have.


It is recommended that you purchase a NoPass instead of a PassMe or PassMe 2 passthrough device if you are unsure which to get, as they are guaranteed to work with all Nintendo DS systems, past, present, and future. If you have a friend with a newer DS who may need to borrow your NoPass to play your games, it's always helpful to have a universal method of running them.

This device has very recently come to market, but is already making a big impression in the community. The Cyclo DS Evolution has the best looking and functioning menu system of the three devices, in my opinion. And if you don't like it, the menus are skinnable with a number of skins already available for the device. You might be thinking, "Why would I care what my menus look or act like?" Well, when you use the device to load your software over and over and over again, bad menus really piss you off. When you can't use the touch screen to scroll up and down and select items and have to use the keys, but holding down the keys doesn't make the menu scroll by and you have to press the up and down buttons a lot to get to your software, you get pissed off. And yes, there really are menus that are this bad.

Aside from the pleasant menus, the Cyclo DS Evolution supports DLDI. It also features on the fly DLDI patching. I dislike having to patch my games on my PC before loading them onto my microSD card, so this is a great feature.

My favorite feature of the Cyclo DS Evolution is the "remember what I loaded last time and load that same program again" feature. Simply holding down L and R at boot will boot the last thing that was booted. This enables me to avoid navigating the menu system when I'm debugging a program and running it repeatedly for testing.

The Cyclo DS Evolution also has a NoPass mode where it will act as a NoPass to boot your GBA flash carts, a GBA Movie Player, or any other Slot-2 device.

As you've probably noticed, as I've written the most about the Cyclo DS Evolution, that I like it the best of the three devices. It is competitively priced with the others, which is good. While it doesn't support running GBA software, it feature rich on the Nintendo DS side of things. I recommend the Cyclo DS Evlolution to meet all of your Slot-1 needs and desires.

Installing devkitARM is quite simple. Directions are already documented on their website. Visit http://www.devkitpro.org/setup.shtml for directions. Although more geared towards Windows, the installation is fairly straight forward. Automated installers are available for Windows, Macintosh, and Linux.

libnds's source install is less documented than devkitPro's source install, but is also quite simple in comparison. libnds is automatically installed by the automated installers of devkitARM. However, if you want to view the libnds source code, you'll have to install it from source.

Procedure 4.1. To install libnds from source

  1. Simply download the latest source from SourceForge.net.

  2. Extract it to $DEVKITPRO/libnds.

                            patater@patater.com:~$mkdir
                            $DEVKITPRO/libnds
                            patater@patater.com:~$mv
                            libnds-src-*.tar $DEVKITPRO/libnds/
                            patater@patater.com:~$cd
                            $DEVKITPRO/libnds
                            patater@patater.com:~$tar
                            -xvjf libnds-src-*.tar.bz2 $DEVKITPRO/libnds
                        
  3. Change your current directory to $DEVKITPRO/libnds and type make.

                            patater@patater.com:~$cd
                            $DEVKITPRO/libnds
                            patater@patater.com:~$make
                        
  4. If devkitARM is installed properly, libnds will compile in a matter of seconds and you'll be on your way to developing software for the Nintendo DS.

Since the beginning of time, humans have used raster displays to draw electronic images. Raster images aren't used too much anymore, in practice. However, most all displays still act like raster displays from a programming perspective. Each physical display on the Nintendo DS is actually an LCD screen which works nothing like a raster display in actuality, but the electronics that control it provide an interface to the graphics engines on the Nintendo DS that is very much like a raster display.

So what is a raster display? Put simply, it is just like a television display. A beam of electrons blasts away at the back of a phoshor coated screen in a deterministic way (known as a raster scan). The beam, from the perspective of a person watching the television, travels from left to right, one scan line at a time. The beam never blast electrons from right to left. After the beam reaches the right edge of the screen, it turns off and moves to the right and down one line. When the beam finally reaches the bottom line of the screen, it travels back up to the upper left of the screen and begins this drawing process all over again. Figure 5.1, “The Raster Display” illustrates this process in an exaggerated fashion.

Two things are important to remember here. First, that the period of time during which the beam goes from right to left (not drawing anything) is called the horizontal blanking period, or hblank. Second, that the period of time during which the beam goes from bottom to top (again, not drawing anything) is called the vertical blanking period, or vblank. Knowing about vblank is useful for us as Nintendo DS programmers because it is the period of time in which we will tell the Nintendo DS to draw things. If we didn't, then the Nintendo DS display might be in the middle of drawing the screen when we tell it what to draw. This would give us strange artifacts and is generally undesirable.


Since this is the first time in this manual where we'll start to write code, we should be aware of the resources available to assist us in following along with this manual. If you haven't already done so, download the sources that accompany this manual from the manual homepage at http://patater.com/manual. After extracting the sources, you'll find a folder called code. The manual provides a template for you to use with this manual and any other Nintendo DS programming projects you might wish to start developing. This template is located in the code folder and is itself a folder called chapter_0-starting_system. The code folder also contains project folders for each chapter. If at anytime you get stuck or if you want to skip a chapter, feel free to refer the completed project for the chapter you are stuck on or to grab the completed project for the chapter prior to the one you wish to skip to. To follow along with this manual, copy the chapter_0-starting_system folder to a nice place you wish to work from (I'd copy the folder to my ~/projects directory and name the copy manual) and open source/main.cpp with your favorite text editor. (My favorite text editor is gvim.) Let's get going!

After we get the basic setup done, we now have to tell the graphics engine where to get its display data from. The two graphics engines share the same VRAM; i.e. There are not two VRAM A banks, one for the main screen and one for the sub screen. We'll use these memory locations when we load the graphics later. Let's make a function called initVideo.

void initVideo() {
    /*
     *  Map VRAM to display a background on the main and sub screens.
     * 
     *  The vramSetMainBanks function takes four arguments, one for each of the
     *  major VRAM banks. We can use it as shorthand for assigning values to
     *  each of the VRAM bank's control registers.
     *
     *  We map banks A and B to main screen background memory. This gives us
     *  256KB, which is a healthy amount for 16-bit graphics.
     *
     *  We map bank C to sub screen background memory.
     *
     *  We map bank D to LCD. This setting is generally used for when we aren't
     *  using a particular bank.
     */
    vramSetMainBanks(VRAM_A_MAIN_BG_0x06000000,
                     VRAM_B_MAIN_BG_0x06020000,
                     VRAM_C_SUB_BG_0x06200000,
                     VRAM_D_LCD);

    /*  Set the video mode on the main screen. */
    videoSetMode(MODE_5_2D | // Set the graphics mode to Mode 5
                 DISPLAY_BG2_ACTIVE | // Enable BG2 for display
                 DISPLAY_BG3_ACTIVE); //Enable BG3 for display

    /*  Set the video mode on the sub screen. */
    videoSetModeSub(MODE_5_2D | // Set the graphics mode to Mode 5
                    DISPLAY_BG3_ACTIVE); // Enable BG3 for display
}
            

There are nine VRAM banks in total on the Nintendo DS. See Table 5.2, “VRAM Bank Information” for details about them. Our 16bit background images take up 128KB of memory each. Thus, each background has to have one whole VRAM bank assigned to it. Not all VRAM banks can be used for all purposes, however. Refer to Appendix A, for more detailed information.


libnds helps us once again by provide a nice <glossaryterm>API</glossaryterm> for accessing the affine transformation matrix of a particular affine background. libnds provides access to a background's affine transformation matrix through four variables. Figure 5.2, “libnds Affine Background API” shows the names of these variables and which part of the affine transformation matrix they align with.


What we'll do now is add three backgrounds. We'll put a splash screen on the top physical screen, a starfield on the bottom physical screen, and a planet placed atop the starfield background. To do this, we'll use SUB_BG3 (although we could use SUB_BG2) for the splash screen and both backgrounds 2 and 3 on the main screen for the planet and starfield respectively. In order to make sure the planet shows up above the starfield as opposed to below it, we give the planet a priority number less than that of the starfield's priority number. Relatively lower priority numbers place backgrounds relatively above other backgrounds. There are only four priority numbers per graphics engine that we can assign to backgrounds (priority numbers 0-3).

We'll now use that nice API libnds provides us for both the background control registers and the affine transformation matrix. Let's proceed to make a function called initBackgrounds which will set up our affine backgrounds. Explanations of what is going on is the comments.

void initBackgrounds() {
    /*  Set up affine background 3 on main as a 16-bit color background */
    BG3_CR = BG_BMP16_256x256 |
             BG_BMP_BASE(0) | // The starting place in memory
             BG_PRIORITY(3); // A low priority

    /*  Set the affine transformation matrix for the main screen background 3
     *  to be the identity matrix.
     */
    BG3_XDX = 1 << 8;
    BG3_XDY = 0;
    BG3_YDX = 0;
    BG3_YDY = 1 << 8;

    /*  Place main screen background 3 at the origin (upper left of the screen)
     */
    BG3_CX = 0;
    BG3_CY = 0;


    /*  Set up affine background 2 on main as a 16-bit color background */
    BG2_CR = BG_BMP16_128x128 |
             BG_BMP_BASE(8) | // The starting place in memory
             BG_PRIORITY(2);  // A higher priority

    /*  Set the affine transformation matrix for the main screen background 3
     *  to be the identity matrix.
     */
    BG2_XDX = 1 << 8;
    BG2_XDY = 0;
    BG2_YDX = 0;
    BG2_YDY = 1 << 8;

    /*  Place main screen background 2 an interesting place
     */
    BG2_CX = -(SCREEN_WIDTH / 2 - 32) << 8;
    BG2_CY = -32 << 8;

    /*  Set up affine background 3 on sub as a 16-bit color background */
    SUB_BG3_CR = BG_BMP16_256x256 |
                 BG_BMP_BASE(0) | // The starting place in memory
                 BG_PRIORITY(3); // A low priority

    /*  Set the affine transformation matrix for the sub screen background 3
     *  to be the identity matrix.
     */
    SUB_BG3_XDX = 1 << 8;
    SUB_BG3_XDY = 0;
    SUB_BG3_YDX = 0;
    SUB_BG3_YDY = 1 << 8;

    /*
     *  Place main screen background 3 at the origin (upper left of the screen)
     */
    SUB_BG3_CX = 0;
    SUB_BG3_CY = 0;
}
            

DMA stands for Direct Memory Access. DMA allows the reading and writing of memory independently of the CPU. The Nintendo DS has special, dedicated DMA hardware to do quick and moderately effcient moving of memory. DMA is not very efficient for memory fill operations, however, as the data to fill with needs to be read once for every write. Libnds provides us with a few functions to make use of the DMA hardware in the Nintendo DS.

Whenever you have the opportunity to use DMA, you should. It is always better to use DMA than to use a for loop to copy data. When using DMA to copy from main memory, do not forget to flush main memory before using DMA. The DMA doesn't use the cache where the relevant memory may currently be stored, so flushing to main memory guarantees that DMA sees the correct data. Another issue to consider would be that in the middle of a DMA, the main CPUs are prevented from using certain memory hardware. This can cause awkward bugs with interrupt handling. For this reason, and swifastcopy may be safer, and is not too much slower. The safest bet is always memcopy and memset, if you are running into some bugs.

The declaration of dmaCopyHalfWords from libnds is as follows.

static inline void dmaCopyHalfWords(uint8 channelconst void * sourcevoid * destuint32 size); 

In our program, we will use dmaCopyHalfWords to load some graphics into memory. We use the function dmaCopyHalfWords instead of dmaCopy because it is more explicit as to how it is copying. We'll use the same channel (channel 3) that the ordinary dmaCopy uses, though. It also let's us specify which DMA channel to use when copying. Let's start out by writing some functions to display our backgrounds. Since we've already set up the hardware to display the data in the desired manner, right after the copy we will get some nice images displayed on our screens. If we didn't set up our backgrounds first, we'd most likely get garbage on the screen until we eventually did set up the background control registers.

/* Select a low priority DMA channel to perform our background
 * copying. */
static const int DMA_CHANNEL = 3;

void displayStarField() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     starFieldBitmap, /* This variable is generated for us by
                                       * grit. */
                     (uint16 *)BG_BMP_RAM(0), /* Our address for main
                                               * background 3 */
                     starFieldBitmapLen); /* This length (in bytes) is generated
                                           * from grit. */
}

void displayPlanet() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     planetBitmap, /* This variable is generated for us by
                                    * grit. */
                     (uint16 *)BG_BMP_RAM(8), /* Our address for main
                                               * background 2 */
                     planetBitmapLen); /* This length (in bytes) is generated
                                        * from grit. */
}

void displaySplash() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     splashBitmap, /* This variable is generated for us by
                                    * grit. */
                     (uint16 *)BG_BMP_RAM_SUB(0), /* Our address for sub
                                                   * background 3 */
                     splashBitmapLen); /* This length (in bytes) is generated
                                        * from grit. */
}
            

The default template makefile will turn your graphic files into object files for linking into your program. Never include data as a header file.

The graphics must be in a lossless image format, such as gif, tif, bmp, or png in order to work with the provided template makefile. I prefer the png graphic format. Image conversion is usually done by a program called grit. The provided template makefile will ask grit to convert images in the gfx folder of your project root to a format ready for the Nintendo DS.

The provided template makefile, adapted from the default libnds template makefile, is a good base for most all projects. It will look in a folder called gfx (in the same directory as the makefile) for your graphics. If any are found, it uses a special bin2o rule to tell grit to turn your images into .o files, according to grit rule files (with the .grit files extension), which can be linked into your program. grit will create a header file (.h) for your data. The name format for them works like so: if a file is called orangeShuttle.png the header file will be called orangeShuttle.h. Inside this header file will be a reference to the data in the .o, named orangeShuttleTiles and orangeShuttlePal or orangeShuttleBitmap, depending on how the grit file specifies which format to convert your image into. It will also include the length in bytes of the data references as orangeShuttleTilesLen and orangeShuttlePalLen or orangeShuttleBitmapLen.

For our project, we'll be putting the our graphic files and grit rule files into the gfx directory and having the makefile use grit on them.

Sprites are broken into 8x8 pixel pieces. This is called tiling. When drawn to screen, the hardware pieces these tiles together, like a puzzle where the pieces have no distinguishing edges. There are two ways to tile sprites, 1D and 2D. In a 2D layout, the sprite memory is treated like a giant image from which sprite tiles are obtained by making a box the size of the sprite and placing it over the giant image. In a 1D layout, the sprites are layed out in a linear fashion, as discussed in Figure 6.1, “ The upper text shows information as it would be on a non-tiled background. The lower text shows the same data, tiled, for use in tiled graphic modes. ”.

The conversion process is very similar to that for backgrounds. We simple make grit rule files that tell grit how we want our images converted, and it generates a .o and a header file for us. The grit rule files and image files go into the same place as background images, the gfx> folder.

Figure 6.1.  The upper text shows information as it would be on a non-tiled background. The lower text shows the same data, tiled, for use in tiled graphic modes.

const u16 data[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020,
0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,
0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040,
0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,
0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060,
0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,
0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0,
0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0,
0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0,
0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF};
const u16 data[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
0x2020, 0x2020, 0x2020, 0x2020, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,
0x4040, 0x4040, 0x4040, 0x4040, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,
0x6060, 0x6060, 0x6060, 0x6060, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,
0x0000, 0x0000, 0x0000, 0x0000, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
0x2020, 0x2020, 0x2020, 0x2020, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,
0x4040, 0x4040, 0x4040, 0x4040, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,
0x6060, 0x6060, 0x6060, 0x6060, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,
0x8080, 0x8080, 0x8080, 0x8080, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF,
0x8080, 0x8080, 0x8080, 0x8080, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF};
                

Up until now, we haven't used the fancy SpriteEntry union included in libnds. It allows us to avoid thinking about bit twiddling and masking in most cases. In the case of showing and hiding sprites, we still need to be thinking about these bits due to an oddity in the Nintendo DS hardware: the hiding bit of a sprite (bit 9 of sprite attribute 0) is also the double bound bit of a sprite if the sprite is an affine sprite (bit 8 of sprite attribute 0). Follow along with the comments and code as we formulate a solution to writing a function which shows and hides all kinds of sprites.

void setSpriteVisibility(SpriteEntry * spriteEntry, bool hidden, bool affine,
                         bool doubleBound) {
    if (hidden) {
        /*
         * Make the sprite invisible.
         * 
         * An affine sprite cannot be hidden. We have to turn it into a
         * non-affine sprite before we can hide it. To hide any sprite, we must
         * set bit 8 and clear bit 9. For non-affine sprites, this is a bit
         * redundant, but it is faster than a branch to just set it regardless
         * of whether or not it is already set.
         */
        spriteEntry->isRotoscale = false; // Bit 9 off
        spriteEntry->isHidden = true; // Bit 8 on
    } else {
        /* Make the sprite visible.*/
        if (affine) {
            /* Again, keep in mind that affine sprites cannot be hidden, so
             * enabling affine is enough to show the sprite again. We also need
             * to allow the user to get the double bound flag in the sprite
             * attribute. If we did not, then our sprite hiding function would
             * not be able to properly hide and restore double bound sprites.
             * We enable bit 9 here because we want an affine sprite.
             */
            spriteEntry->isRotoscale = true;

            /* The double bound flag only acts as the double bound flag when
             * the sprite is an affine sprite. At all other times, it acts as
             * the sprite invisibility flag. We only enable bit 8 here if we want
             * a double bound sprite. */
            spriteEntry->rsDouble = doubleBound;
        } else {
            /* Bit 9 (the affine flag) will already be off here, so we don't
             * need to clear it. However, bit 8 (the sprite invisibility flag)
             * will need to be cleared. */
            spriteEntry->isHidden = false;
        }
    }
}
            

We'll need to make a place for our sprite data to live in VRAM. Since we will be using sprites on the main graphics engine, we can use VRAM bank E for our sprites. VRAM bank E is smaller than the other VRAM banks we've dealt with so far, as it is only 64 KB in size. However, this is more than enough to store 1024 unique 16-color tiles.

In our initVideo, we need to map VRAM bank E for use with sprites on the main graphics engine. Then, we need to tell the main engine to enable sprites of the tiling style we want. We will use 1D tiled sprites. The resulting initVideo function, post-modifications, is presented below .

void initVideo() {
    /*
     *  Map VRAM to display a background on the main and sub screens.
     * 
     *  The vramSetMainBanks function takes four arguments, one for each of the
     *  major VRAM banks. We can use it as shorthand for assigning values to
     *  each of the VRAM bank's control registers.
     *
     *  We map banks A and B to main screen  background memory. This gives us
     *  256KB, which is a healthy amount for 16-bit graphics.
     *
     *  We map bank C to sub screen background memory.
     *
     *  We map bank D to LCD. This setting is generally used for when we aren't
     *  using a particular bank.
     *
     *  We map bank E to main screen sprite memory (aka object memory).
     */
    vramSetMainBanks(VRAM_A_MAIN_BG_0x06000000,
                     VRAM_B_MAIN_BG_0x06020000,
                     VRAM_C_SUB_BG_0x06200000,
                     VRAM_D_LCD);

    vramSetBankE(VRAM_E_MAIN_SPRITE);

    /*  Set the video mode on the main screen. */
    videoSetMode(MODE_5_2D | // Set the graphics mode to Mode 5
                 DISPLAY_BG2_ACTIVE | // Enable BG2 for display
                 DISPLAY_BG3_ACTIVE | // Enable BG3 for display
                 DISPLAY_SPR_ACTIVE | // Enable sprites for display
                 DISPLAY_SPR_1D       // Enable 1D tiled sprites
                 );

    /*  Set the video mode on the sub screen. */
    videoSetModeSub(MODE_5_2D | // Set the graphics mode to Mode 5
                    DISPLAY_BG3_ACTIVE); // Enable BG3 for display
}
            

We'll be using the same memory alignment (boundary) as the GBA uses for our sprites. Tile VRAM addresses must be aligned to 32 bytes. If you feel shorted by this, since you can't use all 1024 addressable tiles when using 256 color tiles, for instance, then you can look up how to use other alignments at http://nocash.emubase.de/gbatek.htm#dsvideoobjs. You'll have to set REG_DISPCNT (via videoSetMode) with a value defined in libnds/include/nds/arm9/video.h akin to DISPLAY_SPR_1D_SIZE_XXX (the default, and the method the GBA and we use, is DISPLAY_SPR_1D_SIZE_32).

To compute the address to copy tiles to, we basically need to know two things: the memory alignment we are using and the tile numbers we want to assign data to. Using the formula from Martin Korth's GBATEK, "TileVramAddress = TileNumber * BoundaryValue", and libnds's SPRITE_GFX define we can compute the address of any tile as follows.

static const int BOUNDARY_VALUE = 32; /* This is the default boundary value
                                       * (can be set in REG_DISPCNT) */
static const int OFFSET_MULTIPLIER = BOUNDARY_VALUE /
                                     sizeof(SPRITE_GFX[0]);
uint16 * tileVramAddress = &SPRITE_GFX[shuttle->tileIdx *
                                           OFFSET_MULTIPLIER];
            

We usually want to copy more than one tile into vram at a time, however. Luckily, when converting images to sprites with grit, it will tell us the length in bytes of our tile data for that sprite. After we have the length in bytes, we can use dmaCopyHalfwords (which uses byte lengths for copying) to copy the tile data into VRAM. We can also calculate how many tiles an image uses from its length in bytes by diving the length in bytes by how many bytes a tile takes up. In our case, as we'll be using 16-color tiles, a tile (8x8 pixels) takes up 32 bytes.

Now, to see a sprite in action. Let's load in the orangeShuttle graphic and the moon graphic. Make a new function called initSprites. Place it after the initBackgrounds function. Make sure to include orangeShuttle.h and moon.h now. They contain information about our sprites as generated by grit.

I've also create a new struct type called "SpriteInfo". This struct contains information about sprites that aren't explicitly contained in the SpriteEntry struct. We'll be using it to help us manage our information about sprites better.

We'll just follow these steps twice when writing the initSprites function for our sprites. You can follow along with the comments and the code below.

void initSprites(tOAM * oam, SpriteInfo *spriteInfo) {
    /*  Define some sprite configuration specific constants.
     * 
     *  We will use these to compute the proper index into memory for certain
     *  ti