Partie 5 : La SRAM

Développement amateur sur Nintendo DS Partie 5 - La SRAM

Les cartouches Nintendo Gameboy Advance possèdent un emplacement nommé SRAM qui est utilisé pour sauvegarder des données provenant des jeux. Ces données peuvent être écrites par les programmes GBA et restent présentes même après l'extinction de la Gameboy Advance.

Cet emplacement peut aussi être lu et écrit par la Nintendo DS. Ceci permet à un programme amateur de stocker des données qui peuvent être reprises après l'arrêt et relance de la machine. Cela peut aussi permettre à un programme de 'télécharger' des données qu'il va utiliser. On peut réaliser tout cela en utilisant notre logiciel livré avec la cartouche Flash pour 'sauvegarder' les données sur la cartouche. Ou pour les retrouver.

Une table des high score d'un jeu peut être un exemple d'utilisation de cette mémoire. Ou pour un programme de type Notepad, stocker le texte dans la SRAM, permettant à l'utilisateur de le télécharger ensuite avec son logiciel livré avec sa cartouche Flash. Ainsi, lui autorisant ensuite de remettre de nouveaux textes après les avoir modifiés sur PC.

Ce tutorial explique comment lire et écrire à cet emplacement mémoire.

Organisation mémoire de la cartouche GBA

Le Wiki NDSTech nous montre l'organisation mémoire de l'ARM9. La ROM de la cartouche GBA est définie à l'adresse 0x08000000 et la SRAM à l'adresse 0x0A000000. La taille de la SRAM est de 64KB.

La mémoire de la cartouche GBA doit être redirigé vers un processeur en particulier. Seulement l'un deux 2 (ARM7 et ARM9) peut accéder à cette mémoire à un instant t. Pour contrôler vers quel processeur la mémoire doit être redirigée, nous utilisons le registre WAIT_CR.

Le Bit 7 de ce registre doit être positionné si l'on souhaite que l'ARM7 accède à la mémoire, ou effacé (mis à 0) si c'est l'ARM9.

Voici un exemple de code montrant le façon de procéder :

/* Garde une copie memoire de certaines donnees de la cartouche */
static char card_id[5];

// Autorise l'ARM9 a cceder a l'emplacement memoire de la cartouche en
// effacant le bit 7.
WAIT_CR &= ~0x80;

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

ID de la cartouche

Un post de Darkain sur GBADEV identifie un problème sur le code d'un développement amateur écrit en SRAM sans d'abord s'identifier quand la cartouche est insérée. Si le code a été téléchargé via Wifime, il peut ne pas y avoir de cartouche flash installée. Cela peut être une cartouche du commerce avec des données de jeu sauvegardées importantes. C'est pour cela que le code amateur ne doit pas écraser les données sans avoir l'autorisation de le faire.

Pour empêcher que cela n'intervienne, Darkain suggère de tester un identifiant qui se trouve sur toutes les cartouches pour voir s'il contient le mot 'PASS'. Ce code est utilisé par tous les développements amateur et est hérité de la méthode initiale du 'PassME' pour exécuter du code amateur. l est très facile de tester cet identifiant et je recommande fortement de le faire comme conseillé sur le forum avant toute écriture en SRAM.

Une fois que l'on a déterminé qui accédait à l'emplacement mémoire, on peut lire l'identifiant. L'emplacement mémoire 0x080000AC contient cet identifiant sous la forme d'une chaine de caractères de 4 octets. Dans cet exemple, je copie les données de la cartouche mémoire vers une variable locale :

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()
{
    [...]

    /* Copie de l'identifiant de 4 caracteres dan sun variable locale.
       Cela doit etre 'PASS' pour tous les developpements amateur.
       L'identifiant se trouve a l'emplacement memoire 0x080000AC.*/
    memcpy(card_id, (char*)0x080000AC, 4);

    [...]
}

Pour tester le code 'PASS', j'utilise la fonction is_homebrew_cartridge :

/* Retourne true si l'identifiant de la cartouche est '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();
    }
}

Lecture et écriture en SRAM

Une fois redirigé, la SRAM se trouve à l'emplacement mémoire 0x0A000000. libnds possède une macro, SRAM, qui est définie pour pointer à cet endroit :

#define SRAM          ((uint8*)0x0A000000)

Cette macro est définie en unint8 pour une bonne raison. Toutes les lectures et écritures en SRAM se font par 8 bits uniquement à chaque fois. Les lectures de 16 ou 32 bit en une fois ne fonctionnent pas. C'est pour cette raison que j'ai utilisé la fonction memcpy tout à l'heure, qui le fait octet par octet à chaque fois.

L'écriture dans cette mémoire peut être faite dans n'importe quel format. Vous pouvez stocker du texte, des données binaires, etc ... Cela peut être une bonne idée de stocker un identifiant avant d'écrire quelque chose pour éviter de lire accidentellement des données écrites par un autre programme.

Ce tutorial ne fait que lire et écrire des chaînes à zéro terminal. En appuyant sur 'A', la chaîne est lue de la SRAM et stockée dans une variable locale, sram_data, qui est ensuite affichée à l'écran :

if(READ_KEYS & KEY_A)
{
    /* Copie de la SRAM vers notre variable a afficher */
    memcpy(sram_data, (char*)SRAM, sizeof(sram_data) - 1);
}

Si le 'B' est appuyé, la chaîne est copiée de la variable locale vers la SRAM :

if(READ_KEYS & KEY_B)
{
    /* Copie une chaine vers la SRAM sir le code est ok. */
    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));
}

Construction de l'exécutable

La lecture et écriture en SRAM requiert l'utilisation du DevKitPRO release 13 ou plus. Les versions précédentes ne possédaient le code adéquate pour faire fonctionner les permissions d'accès en SRAM de façon satisfaisante.

Le programme se nomme 'sram_demo1'. Le code ARM9 se trouve dans arm9_main.cpp. Il utilise la sortie console pour afficher les informations, de la même façon que dans le premier tutorial. Le code ARM7, dans arm7_main.cpp est exactement le même que celui du template livré avec libnds. Je n'ai rien modifié du tout. Un Makefile basique est livré pour tout construire.

Le code source complet est disponible dans sram_demo1.zip et vous pouvez télécharger les fichiers sram_demo1.nds et sram_demo1.nds.gba pour le fonctionnement sur émulateur et hardware.

Test

Voici les étapes à suivre pour prouver que la lecture et écriture en SRAM fonctionne :

  1. La première fois que vous lancez sram_demo1, appuyez sur 'B' pour copier le texte en SRAM. Vous devriez voir le message 'Copy Succeeded'. Ensuite, appuyez sur 'A' pour lire le contenu de la SRAM et l'afficher sur l'écran. Vous devriez voir 'Hello from SRAM!'. Redémarrez la DS.
  2. La seconde fois, appuyez sur 'A'. Cela devrait provoquer l'affichage de 'Hello from SRAM!', le contenu du message copié en SRAM lors du premier essai. Cela prouve que le contenu de la SRAM est resté après le redémarrage de la DS.
  3. Téléchargez le contenu de la SRAM sur votre PC avec le logiciel livré avec votre cartouche flash. Editer le pour afficher un autre texte. Remettez les données sauvegarder dans la DS et relancez sram_demo1. Appuyez de nouveau sur 'A' et vous devriez voir le texte que vous avez édité.

Conclusion

Ce tutorial vous a montré comment lire et écrire en SRAM d'une cartouche FLASH GBA. Je suis cependant pas encore sur de certaines façons de faire :

  • Une fois, lorsque je redirigeais la mémoire en écrivant dans WAIT_CR, j'ai lu tout de suite la mémoire, j'ai eu des données complètement erronées. Je dois temporiser un peu ma lecture pour obtenir les bonnes données. Malheureusement, je n'ai pas été capable de le reproduire. Il y a peut-être un besoin d'attendre la fin de la redirection mémoire ?
  • Dans mon programme d'exemple, je laisse la mémoire SRAM redirigée. Peut on inverser la redirection ? En remettant le bit 7 de WAIT_CR à sa valeur précédente ?
  • Certaines cartouches flash possèdent plusieurs zones de sauvegarde. Est-il possible de les utiliser en accédant à d'autres registres spécifiques aux cartouches ?

Je souhaite remercier les personnes qui postent sur les forums GBADEV, où j'ai appris la plupart des informations reproduites dans ce tutorial.

Les mises à jour de ce tutorial peuvent être obtenues sur le weblog de l'auteur (Chris Double) : http://radio.weblogs.com/0102385. Il peut être contacté à l'adresse chris.double@double.co.nz