Home New Tutorials Programs
1-R4DS Setup 2-Software Space Invaders
Old Tutorials DSEmu
1-Setup 4-Sound 7-FIFO
2-Framebuffer 5-SRAM 8-Interrupts
3-Keys 6-Filesystems 9-Microphone
10-Extended Rotation Background

Homebrew Nintendo DS Development Part 5 - SRAM

This tutorial was tested and works with libnds dated 2005-10-18. Unfortunately it no longer works with libnds dated 2005-12-12. I'm looking into it.

Nintendo Gameboy Advance cartridges have an area called SRAM which is used for storing saved game data. This data can be written to by GBA programs and it will remain there when the Gameboy Advance is turned off.

This area can also be read from and written to on the Nintendo DS. This allows a homebrew program to store data that remains persistent between switching the machine off and on. It also allows the program to allow users to 'upload' data for the program to use. They can do this by using their Flash Cartridge software to 'save' the data into this area. Or retrieve it.

Example usages for this memory area might be a high score table for a game. Or for a notepad program to store the text in the SRAM area allowing the user to download it with their Flash Cartridge software. And allowing them to upload new text with that software after they've edited it on the PC.

This tutorial shows how to read and write to that area.

GBA Cartridge Memory Layout

The NDSTech Wiki shows the memory layout of the ARM9. The GBA cartridge ROM is located at address 0x08000000 and the SRAM area is at 0x0A000000. The SRAM is 64KB in size.

The GBA cartridge memory must be mapped to a particular processor. Only one of the ARM7 and ARM9 can access it at a time. To control which processor currently has the memory mapped we use the WAIT_CR register.

Bit 7 of this register should be set if the ARM7 is to have access to the GBA cartridge memory, or cleared if the ARM9 has access.

Here is some sample code to show how this works:

  /* Hold a local copy of some card data */
  static char card_id[5];

  /* Enable the ARM9 to access the GBA cartridge memory area by
  /* clearing bit 7. 
  WAIT_CR &= ~0x80;

  /* It is now safe to read GBA Cartridge memory */
  memcpy(card_id, (char*)0x080000AC, 4);

Cartridge ID

A GBADEV forum posting from Darkain identified a problem if homebrew code wrote to the SRAM area without first identifying what cartridge is inserted. If the homebrew code was downloaded via Wifime, there may not be a flash cartridge installed with homebrew code. It may be a commercial cartridge with important save game data. The downloaded homebrew program shouldn't erase that data without permission.

To prevent this from happening Darkain suggested checking an identifier that is in all cartridges to see if it contains 'PASS'. This code is used by all current homebrew code and relates to the original 'PassME' method of running homebrew. It is very simple to check for this identifier and I recommend following the advice of the forum posting to check before writing to SRAM.

Once the cartridge memory is mapped we can read this identifier. Location 0x080000AC contains the identifier as a non-null terminated string of length 4 bytes. In this example I copy the data from the cartridge memory to a local variable:

  static char card_id[5] = { 0,0,0,0,0 };

  static void memcpy(char* dest, char const* src, int size) {
    while(size--) 
      *dest++ = *src++;
  }

  void main() {
    [...]

    /* Copy contents of the 4 character ROM identifier into a local
       variable. This should be 'PASS' for all homebrew ROM's. The
       identifier is located at 0x080000AC.*/
    memcpy(card_id, (char*)0x080000AC, 4);
  
    [...]
  }

To check for the 'PASS' code I use a function, is_homebrew_cartridge:

  /* Return true if the card id is 'PASS' */
  static int is_homebrew_cartridge() {
    return 
      card_id[0] == 'P' &&
      card_id[1] == 'A' &&
      card_id[2] == 'S' &&
      card_id[3] == 'S';
  }

  void some_func() {
    if(is_homebrew_cartridge()) {
      write_to_sram();
    }
  }

SRAM reading and writing

Once mapped, SRAM is located at memory address 0x0A000000. libnds has a macro, SRAM, which is defined to point to this area:

#define SRAM          ((uint8*)0x0A000000)

The macro is typed as an 8 bit type for a reason. All reads and writes to SRAM must be 8 bits at a time. 16 bit or 32 bit reads will not work. This is why the memcpy routine I showed earlier does it a byte at a time.

Writing to this memory area can be done in any format. You can store text, binary data, etc. It might be a good idea to store some data at the beginning of the area to identify the structure of the data in case you accidentally read SRAM data saved by another program.

This tutorial just reads and writes zero terminated strings. On pressing the 'A' key the string is read from SRAM and stored in a local variable, sram_data, which is displayed on screen:

    int keys = ~REG_KEYINPUT;
    if(keys & KEY_A) {
      /* Copy from SRAM to our local variable to display */
      memcpy(sram_data, (char*)SRAM, sizeof(sram_data) - 1);
    }

If the 'B' key is pressed then a static string is copied from a local variable and stored in SRAM:

    if(keys & KEY_B) {
      /* Copy a string of text to SRAM if the cartridge has a homebrew
	 ROM in it. */
      const char text[] = "Hello from SRAM!";
      const char error[] = "Not Homebrew! Copy failed.";
      const char success[] = "Copy succeeded.";
      if(is_homebrew_cartridge()) {
	memcpy((char*)SRAM, text, sizeof(text));
	memcpy(copy_status, success, sizeof(success));
      }
      else
	memcpy(copy_status, error, sizeof(error));
    }

Building the Demo

The complete example program is 'sram_demo1'. The ARM9 code is in arm9_main.cpp. It uses the console routines to print information similar to the first tutorial. The ARM7 code is in arm7_main.cpp. A simple Makefile file is supplied to build everything.

The complete source code is supplied in sram_demo1.zip and you can download the sram_demo1.nds and sram_demo1.nds.gba files for running on the emulators or hardware.

Testing

To prove that SRAM is being written and read from, here's some steps to follow:

  1. The first time you run sram_demo1, press 'B' to copy some text to SRAM. You should see the message 'Copy Succeeded'. Now press 'A' to read the SRAM contents and display it on the screen. You should see 'Hello from SRAM!'. Reboot the DS.
  2. The second time you run the program, press 'A'. It should display 'Hello from SRAM!', the contents that were copied to SRAM from the previous invocation. This proves the SRAM contents survived the reboot.
  3. Download the SRAM contents to the PC using the flash cartridge software. Edit it to display some other text. Put the save data back on the cartridge and re-run sram_demo1. Press 'A' and you should see the text you uploaded.

Conclusion

This tutorial showed how to read and write to the SRAM or saved game area of the GBA flash linker cartridge. I'm still unsure about certain aspects of doing this though:

I'd like to thank the forum posters in the GBADEV forums, where I learnt much of the information for this tutorial.

As always, any comments or suggestions are welcomed. See my contact details below.