vendredi, août 31, 2007

Guru Meditation

Okay. Il y a les jours avec et les jours sans. Et quand il s'agit d'un "guru meditation", on préfère généralement les jours sans :P

Mais 10 ans de programmation d'OS m'ont au moins appris une chose: quand vous tenez un bug, ne le lâchez surtout pas avant d'avoir trouvé d'où il vient. A ce moment alors, et à ce moment seulement, modifiez votre code pour l'éliminer. Ouais, je sais, c'est plus facile à dire qu'à faire. C'est un peu comme jouer à Mario sans s'autoriser à faire marche arrière, ou offrir sa glace à la vanille pour manger un yaourt nature.

Alors si vous n'êtes pas particulièrement porté sur le développement sur DS, vous risquez de trouver tout ceci complètement imbuvable. Auquel cas, je vous propose d'aller voir ma gallerie de geek à la place ... Voilà. Vous êtes prévenus.

If you've ended up here, I bet there are chances your homebrew (or someone else's) threw a "guru meditation" screen at you, which you can basically compare to window's blue-screen-of-death with a smiley reference to Amiga micro-computers. What you see on screen is hexadecimal dump of the DS' internal state: registers and stack. From these, the programmer can try and identify the root cause of the bug. So if the software isn't yours, the best you can do is shooting a screenshot of what's on screen, ensure it is readable and hand that to your favourite homebrew developer with a jar of coffee

Bon, d'abord, j'ai placé un defaultExceptionHandler() le plus tôt possible dans mon programme. Si quelque-chose tourne mal, au moins, j'aurais droit à l'équivalent d'un écran-bleu-windows, mais en rouge. Sans celà, la console se bloque purement et simplement, et on est bon pour débugger à coup de "print toto".
Ensuite, je suis passé faire un petit tour dans le code de gurumeditation.c de la libnds et j'ai trouvé ceci:

//----------------------------
static void defaultHandler() {
//----------------------------
videoSetMode(0);
videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);
vramSetBankC(VRAM_C_SUB_BG);

SUB_BG0_CR = BG_MAP_BASE(31);

BG_PALETTE_SUB[0] = RGB15(31,0,0);
BG_PALETTE_SUB[255] = RGB15(31,31,31);

consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);


En clair, dès qu'un pépin est détecté, la DS va tenter de gérer l'exception et commence par retourner aux modes vidéo par défaut. C'est sympa d'y avoir pensé, mais la ligne en rouge est particulièrement agaçante: elle nous masque complètement l'écran du haut. J'aime autant la commenter: puisque c'est généralement là que se trouve ma console pleine de messages inutiles, j'aurais en vis-à-vis le programme planté et les infos sur où il s'est planté.

Maintenant, l'avantage que l'on a ici sur un écran bleu, c'est qu'on a le code du programme sous la main, de quoi interpréter tous ces chiffres cabalistiques. Voyons un peu.

In order to figure out what went wrong, it may be useful to tweak the code that generate that error report so that it doesn't "kill" your console log (what you've printed with printf). This is shown on the snippet above, which shows the start of defaultHandler, and where I suggest you remove the red line to keep one screen of the DS untouched. Note, too, that in order to have this report ever generated, you'll need defaultExceptionHandler() invoked by your main function -- the soonest possible, the best.


La première chose, c'est de savoir le programme s'est crashé. la valeur qui suit "pc:" correspond à l'adresse fautive dans notre programme. Le plus souvent, ce qui gène le CPU, c'est un accès vers une zone mémoire non-définie (le "data abort"), et l'adresse de cette zone-mémoire est indiquée après "addr:".

La technique la plus rapide consiste à appeler arm-eabi-addr2line -e mybrew.arm9.elf 0x2001156 (merci Thoduv). Quand ça ne suffit pas, j'ajoute "-C" pour le code en C++, ou je reprends mes vieux fichiers .map et je désassemble le code pour y voir plus clair (cf la suite).

Histoire de progresser un peu, je vais passer faire un tour dans le fichier arm9/build/.map qui a été généré automatiquement lors de la compilation de mon programme et qui indique où se trouve quelle fonction/objet pour le CPU.

Petite recherche sur "2001156". Pas de résultat. Evidemment. Par contre, en cherchant juste "2001", je tombe rapidement sur les infos suivantes:

               0x02000bb8                _ZN10SpriteAnimC2Ej
.text 0x02000d00 0x970 SpriteSet.o
0x02000d10 _ZN9SpriteSet6setOAMEtP12sSpriteEntry
0x02000fe4 _ZN9SpriteSet17createDefaultPageEv
0x02000e34 _ZN9SpriteSet8SaveBackEv
0x0200107c _ZN9SpriteSet6unpackEPhj
0x020012c0 _ZN9SpriteSet4LoadEPc
0x020015f8 _ZN9SpriteSet15createEmptyPageEv
0x02000f60 _ZN9SpriteSet7getdataEPht
.text 0x02001670 0x1e8 Wifindows.o
0x02001788 _ZN4Wifi17create_wfc_windowEP6Wind
  1. Notre erreur se trouve quelque part dans le fichier SpriteSet.o (vu qu'il démarre à une adresse inférieure au crash, et que l'unité suivante Wifindows.o commence elle trop loin).
  2. la fonction SpriteSet::unpack est la seule qui pourrait contenir l'adresse fautive.
Bon. On peut maintenant tenter d'aller un peu plus loin en désassemblant le fameux SpriteSet.o, en utilisant "arm-eabi-objdump -drS arm9/build/SpriteSet.o | less". On obtient là un mélange plus ou moins utile de ligne de langage machine et de code C/C++ dans lequel on va chercher l'instruction numéro 456 (0x2001156-0x2000d000=0x456).
 memcpy(palette_target,fptr,pal_hdr.ncols*sizeof(u16));
fptr+=pal_hdr.ncols*sizeof(u16);
iprintf("%i colors loaded\n",pal_hdr.ncols);

44e: 0074 lsls r4, r6, #1 ;; Log. Shift Left Short (r4:=r6*2)
450: 4651 mov r1, sl ;; r1:=r10 -- 'this' (sl='stack limit')
452: 9003 str r0, [sp, #12]
454: 1c22 adds r2, r4, #0 ;; r2==0x2c==22*2
456: 6848 ldr r0, [r1, #4]
458: 1c29 adds r1, r5, #0
45a: f7ff fffe bl 0
45a: R_ARM_THM_CALL memcpy
45e: 1c31 adds r1, r6, #0
460: 484f ldr r0, [pc, #316] (5a0 <.text+0x5a0>) 462: 192f adds r7, r5, r4
464: f7ff fffe bl 0
Pourquoi s'être acharné jusqu'ici, me demanderez-vous ? parce qu'avec ce code désassemblé, on va pouvoir tenter de retrouver le rôle de chaque registre au moment du crash. Lors d'un appel (à memcpy ou à iprintf, par exemple), les arguments seront placés dans r0,r1,r2 ... Je me sers évidemment aussi un peu de ce que je sais des données ... il y a 22 couleurs dans ma palettes, je retrouve "r6: 0x16", ça colle : le registre r6 contient le nombre de couleurs dans la palette avant le crash, et aurait dû être passé comme argument à iprintf un peu plus tard.


Evidemment, c'est le genre de petit jeu qui demande pas mal de connaissance de la machine. Et pour le processeur ARM, j'ai encore pas mal de lacunes ... Mais c'est le genre de trucs que j'adore apprendre, alors dès que j'aurais un peu de papier devant moi, j'irai m'imprimer un le jeu d'instructions du processeur, histoire d'être mieux ARMé pour la suite (ouarf.) et je crois que je vais quand-même refaire quelques "print toto" par-ci par-là pour y voir plus clair ^_^.

oh, pour info, le registre "lr" contient généralement l'instruction ayant fait appel à la fonction en cours ... des fois qu'une erreur dans une bibliothèque vous laisserait dans le vague...

edit: Une petite discussion bien utile avec Bamba, François et Gaël m'a permis de me rendre compte que tout ça, c'était la faute à un pointeur 'this' (r10) non-initialisé avant l'appel de ma fonction SpriteSet::unpack(). Comme quoi, j'ai encore du boulot à faire avant d'être fiable en C++ :P
Read more!

mardi, août 28, 2007

wifi + DS + libmikmod = ^_^

yeah! J'ai réussi à intégrer la nouvelle mouture de libmikmod (et ses drivers DS) de tetattds (sten larsson) avec le petit framework de téléchargement d'image pour la DS.

Résultat ? je peux transférer des fichiers .pcx et les visualiser (enfin, une portion de 256x256) sur la DS, transférer des .mod, des .it et des .s3m et les écouter (à 22KHz), et toujours sauver des fichiers et faire les mises à jour du soft depuis un serveur HTTP.

Ca commence tout doucement à ressembler à une extension de mon PC. C'est probablement encore très loin d'être "user friendly", mais ça prend forme ;)

Ceux qui veulent rejoindre mon cyborg de frère et k93 dans les rangs des béta-testeurs sont les bienvenus sur la page "download" du projet sourceforge . Les impatients qui veulent du code peuvent déjà faire un tour dans le CVS .

"This is a small step for humanity, but a giant leap for me!" ... I now have the latest DS port of libmikmod (from sten larsson's tetattds) integrated with my current beamed-picture-viewer (aka. runME) on the DS. That means in addition to .PCX pictures wifi transfer and display, i can also receive .MOD, .S3M and .IT tracked format (up to 256KB) and play them back on the DS. The ability of software upgrade through HTTP server is still there too and dramatically helps fast development.

The subtle rendering difference might be important for artists working on a PC for the DS platform (e.g. my bross will ultimately write/adapt tunes for DS games, so he'd love to listen what it sounds like without having to compile a binary or depending on an actual game. Still, both PC-server and DS-client sides are far from being intuitive or user-friendly, but that's a small step in the good direction.

As always, code is already available through the Sourceforge CVS if anyone is interested ^_^. I would just appreciate to be notified of it ;)

vendredi, août 24, 2007

The Armaggedon Machine


Tiens, ça fait longtemps que je n'ai plus écrit quelque-chose dans la série "les design réussis des années '90" ... J'ai envie d'y remédier avec le niveau final de commander keen (les maps de tous les niveaux sont toujours sur c-k.com, pour ceux qui auraient loupé mes billets précédents).

Let's pick up a good old DOS platformer: Commander Keen: Goodbye Galaxy. What does the ultimate level look like ? How does it come it gives who finishes the game the feeling of being a true hero ?

"Slick Rippin Keen" pour l'ambiance

Qu'est-ce qui donne à ce dernier niveau le cachet "héroïque" à celui qui le termine ? Comme dans de nombreux autres niveau de Keen, on va devoir récolter des clés pour avancer (cf. les zones encadrées en noir et numérotées).

Dès le début, par contre, John Romero et ses comparses ont frappé fort... très fort. au bout de 10 secondes de jeux, ça crépite déjà de lasers dans tous les sens. Et à moins de connaitre parfaitement le niveau, il faudra traverser ces zones périllieuses (visage qui tire la langue) plus d'une fois.

Like in most levels of the Commander Keen series, the level is designed around the 'search for the keys' principle, but this time, the three keys (see the numbers tagged on the map) are scattered in such a way that you will have to cross repeatedly the most dangerous parts of the level (see the tongue-smiley areas) unless you know the map already. Only with those 4 keys you can enter the Quantum Explosion Dynamo room and proceed to ultimate sabotage (well, watch out for Shikadi Master, though).
And straight from the start, John Romero and his co-workers did stunning level design: you won't be in for 10 seconds that you're already all surrounded by creepy laser flashes.

Seulement une fois les 4 clés récoltées, on pourra passer à la bataille finale (en fait, plutôt le sabotage ultime), à condition de s'être défait du Shikadi Master (entouré, et absent en mode "easy")

Augmenter le ryhtme

Commander Keen est plein de tourelles laser ou autres lance-fléchettes (selon les épisodes). A plus d'un endroit, la difficulté va se corser par rapport aux autres stages simplement par le rythme accéléré des tirs (sans compter l'ajout du stress quand ça crépite dans tous les sens). Idem pour les flames tournantes ou les "écrans bleus" mortels juste après les portes. Plus rapides, ils exigent un maximum de concentration et de précision de la part du joueur. (autant rappeler tout de suite que tout contact est fatal dans ce jeu).

Golden Rule #1: Speed up the rythm. Commander Keen is full of firing lasers (or arrow-spitter, depending on the episode). In many places, just speeding up the fire rate of those lasers increases difficulty by an order of magnitude. Hearing constant fire also adds to the player's stress and feel that he won't be safe anywhere -- a crucial element for a good final level.
Same goes for "blue forceshields" and rotating flames. Once moving faster they suddenly require increased concentration and precision from the player (oh, did i mention all hazard imply instead death, already ?)


Des espaces réduits

De nouveau, l'idée est de restreindre au minimum les marges de manoeuvre. Il est déjà assez délicat de passer sous un de ces "rebondisseurs-inverseurs" en temps ordinaire, alors imaginez quand il y a tout juste la place (au pixel près). Non seulement il faudra choisir au mieux son timing, mais le moindre faux-pas avant ou après l'obstacle nous y conduira tout droit.

Même remarque près de la clé jaune (ouvrez un peu le niveau complet dans une autre fenêtre, va). D'ordinaire, vous avez un shikadi par salle et relativement assez d'espace entre les "poles" qu'il peut électriser. Ici, ils sont deux, ils vont vous mettre la pression et il n'y quasiment pas la place pour les étourdir de tirs entre deux sauts (on peut tirer vers le bas, mais prenez un émulateur et essayez la séquence, vous verrez qu'il vaut mieux passer pacifiquement ici).

Golden rule #2: Small Places. Just have a look at the figure with the bouncing "spinred". Small place, little room for avoiding hazard. It is already stressful to step below spinreds, but here the smallest misplaced toe will kill you. Check your timing twice before running.

Same rule has been applied in the room near the yellow key where you have poles and shikadis. Note again that shikadis can send deathly sparks along those poles! With 4 shots requires to stun one of them and so little safe room around them to hide and shoot, you'll have to run and avoid them.


Des monstres ordinaires en grande quantité

Pourquoi ne mettre qu'un chiot quand on peut en mettre 16 (cf. section 3). Bien sûr, pour le joueur riche en munitions (et suffisament futé que pour faire du tir au pipes *avant* de s'aventurer la-haut ... mais, si je ne me trompe, ces chiens-shikadi peuvent uniquement être assomés temporairement, non ? puisqu'ils rebondissent en tout sens, la marge de manoeuvre -- même pour un dégommeur habile -- sera réduite.
Et quand bien même, ça rajoute à nouveau à la tension "j'aurais jamais assez de tirs", "rhaa ces trucs pullulent", ezv.

Why would you just put *one* doggy when you can put *tons* (see section #3). Of course, for the player who has saved enough amno, it will just be fine to take the backdoor and shot as many of them as you can... Yep, that would do it. Well, if you manage to save amnos, which is certainly not an easy thing in Commander Keen :P And even with enough amno, that's still nice to see that room straight from the start and having the feel that "omg. i already had hard time stunning *one* of them! how am i going to get through *these*"


Des munitions dangereuses

Ne vous contentez pas d'un boss qui demande 500 tirs de laser. Exigez plutôt 5 bombes à retardement. Une arme au maniement plus dangereux, qui ne demande qu'à vous péter à la figure (ou qui impose un temps de recharge au bout d'un certain nombre de tirs, ou de ramasser des éléments proches du boss), c'est bien plus fun.

La machine infernale de CK5 est exactement dans cette veine. Vous allez devoir la faire exploser en attirant vers elle des mines au comportement plutôt chaotique et qui n'exploseront que si vous les touchez vous-même (rangez votre laser: c'est à coup de pogo que ça va se jouer). Autant dire que la présence de maître shikadi dans les environs va vous valoir d'autres sueurs froides...

Last but not least: hazardous amno. Defeating the final Boss with 500 regular laser shots will hardly be fun. Instead, think about a boss that requires 5 delayed grenades. Or something that is hazardous to use and will blast yourself at the first occasion, or a weapon that needs charging before blasting, or using items the boss drops nearby himself. *That* will be fun.

Commander Keen's armaggedon Machine is a good reference in that aspect. You'll have to dismantle it using the shikadi's own mines, which have erratic movements and that explode when you approach them too much. Needless to say that the presence of the Shikadi Master during the process will give you nightmares


Si vous n'y avez jamais joué, installez dosbox , procurez-vous le jeu puis tapez "KEEN5E.EXE /tedlevel 12" une fois prêts.

Voilà. L'Omegamatic n'attend plus que vous!

jeudi, août 23, 2007

Relifting

il y a un moment que j'avais envie de donner une petite touche "plus perso" à l'en-tête de ce blog. C'est chose faite. J'espère que vous aimez . . .

(et oui, je joue à l'apprenti-sorcier avec le CSS ;)
(et oui, le sommet des arbres vient toujours de "minish cap" avec un fond de forêt de "King of Swing". 'faudra que j'y remédie ^_^)

It's been monthes i wish i could re-vamp this blog and give it a more "bilou" look. It's now done. I hope you like it ^_^

(true, i'm still using Minish Cap tree tops and DK-King of Swing background ... this shows what i'd like bilou's forest to look like, not bilou's forest itself :P)

Tags Cloud now supported thanks to phydeaux3 ;)


edit: problème de titres fixés. Les extraits de CSS sont dans les commentaires
edit: et maintenant, grâce à phydeaux3, je peux même vous offrir un zoli nuage de tags plutôt que la liste ridiculement longue du Bloggeur par défaut ^_^

jeudi, août 16, 2007

ARM7 et ARM9 sont sur un bateau ... ARM9 tombe à l'eau. Qui reset?

here: br[here]

I'm trying to make sense of the "execute .nds file" libraries ... Somewhere in an old version of the DSWiki, i found this piece of code (the trick only works with GBamp cartridge, afaik):



REG_IME = IME_DISABLE; // Disable interrupts
WAIT_CR |= (0x8080); // ARM7 has access to GBA cart
*((vu32*)0x027FFFFC) = fileCluster; // Start cluster of NDS to load
*((vu32*)0x027FFE04) = (u32)0xE59FF018; // ldr pc, 0x027FFE24
*((vu32*)0x027FFE24) = (u32)0x027FFE04; // Set ARM9 Loop address
swiSoftReset(); // Reset



which suggests that the software reset will branch the code on 0x27FFE04 at some point, and that the file to be loaded is found at 0x27FFFFC (its first cluster in the FAT flashsystem, at least, which suggests much reverse-engineering of the GBAMP firmware, imho).

J'essaie de comprendre un peu les mécanismes mis en jeu lors du redémarrage de la nintendo DS, et en particulier comment s'y prendre pour lui faire démarrer un fichier .nds précis (lancer le Sprite Editor depuis un programme "menu", par exemple). Une des propositions dans ce sens venait de DSwiki (cf supra), mais ne fonctionne apparemment que pour l'adaptateur GBAmp. Après le reset software, le processeur finit par se placer en 27FFE04, et le mot mémoire @27FFFFC contient le numéro du premier cluster du fichier à démarrer (l'adresse sur le disque, pour ceux qui n'ont pas passé 3 ans à décortiquer le FAT32 ;)

Bien sûr, il est nécessaire d'obtenir la coopération du 2eme processeur, le ARM7 (code ci-dessous), ce qui nous donne à contempler le genre de "magie noire" mise en oeuvre pour synchroniser les deux processeurs de la DS: périodiquement, le ARM7 va inspecter la mémoire (partagée) à l'adresse 27FFE24 (le "pointeur magique"), et si il y trouve l'adresse prévue pour la "boucle Passme", il va déclencher son propre reset.

The trick of course only works if the ARM7 side co-operates, which gives us an idea of how the dual-cpu-with-shared-memory can turn simple things in obscure wizardry:



if (*((vu32*)0x027FFE24) == (u32)0x027FFE04) // Check for ARM9 reset
{
REG_IME = IME_DISABLE; // Disable interrupts
REG_IF = REG_IF; // Acknowledge interrupt
*((vu32*)0x027FFE34) = (u32)0x08000000; // Bootloader start address
swiSoftReset(); // Jump to boot loader
}



In other words, the ARM7 cpu will periodically check whether the "reboot" has been invoked on the ARM9 cpu (in which case, it is trapped in the so-called "passme loop" that consists of a "MAGIC_HERE: jump [MAGIC_POINTER]" instruction where the memory at address MAGIC_POINTER contains the address of the jump instruction, i.e. MAGIC_HERE.

'MAGIC_POINTER (0x27FFE24)' is actually something that is nintendo-firmware related and this is where swiSoftReset() will send us back on the ARM9 cpu. On the ARM7, swiSoftReset() will do the same, but using another magic pointer at 0x27FFE34, which we reprogrammed with the "cartridge entry point" address (0x8000000). Voilà. that should do the trick: ARM9 resets and enters a loop, ARM7 detects it and resets as well. ARM7 then execute the cartridge's initialization as well (which will take care of taking ARM9 out of that loop, hopefully).

Après un petit tour sur "gba&ds tek", il apparaît que le service "softreset" du BIOS de la DS se sert du contenu de 0x027FFE24 (notre "pointeur magique") et de 0x027FFE34 respectivement pour savoir où reprendre le code après la réinitialisation. Pendant que l'ARM9 tourne en rond (le code sur lequel on l'a envoyer lit en permanence le contenu du 'pointeur magique' et s'y rebranche: c'est ça notre "boucle passme" et il suffira à l'ARM7 de modifier le contenu du "pointeur magique" pour débloquer l'ARM9 et de l'envoyer vers le programme de son choix), l'ARM7 relance pour sa part l'exécution vers le point d'entrée de la cartouche en ROM, et l'on devrait se retrouver à nouveau dans la partie du firmware GBAmp qui lance un fichier... sauf qu'il ne s'agira plus de son propre menu, cette fois!

stubs join the game


Now, if we look at the "run .nds file" code in rebootlib or in grizzlyadams' DSchannels, it works a bit differently. The new thing here is the presence of stubs, chunks of code that will help the transition from old software to the new one. The stub will typically take care of filling the RAM with the new program while trying to avoid being destroyed. Some of them simply live on the ARM7 and will thus only support programs that do not use a "custom" loop (basically denying execution of anything that is WiFi-related :-/)

Jetons à présent un oeil au code "executer(char* ndsfilename)" de la rebootlib de lick ou du DSchannels de grizzlyadams. Tout d'abord, ce qui est nouveau ici, ce sont les stubs, des petits bouts de programme qui vont assurer temporairement le chargement entre le programme appelant et le programme appelé. Un stub se charge généralement de préparer l'environnement du nouveau programme et de l'amener en mémoire en prenant soin de ne pas se supprimer lui-même. Dans le plus simple des cas, il s'agira simplement d'un exécutable pour le ARM7 un peu particulier qui se comporte comme le programme-modèle une fois le chargement effectué. L'inconvénient de cette technique, c'est évidemment que les programmes qui ont un "coté 7" spécifique (et notamment faisant appel au WiFi, etc) échoueront lamentablement :-/

In DS channels, grizzlyadams rather use the video memory to store the stub. This of course require special rules for building the stub (as the memory layout of the produced binary has nothing to do with a regular DS environment)

/*
We assume, that VRAM is set up as follows:
- VRAM_A is mapped to 0x6000000 for text screen
- VRAM_B is mapped to 0x6820000 for passed vars
- VRAM_C is mapped to ARM7 WRAM for arm7 stub
- VRAM_D is mapped to 0x6860000 for arm9 stub
*/

(*(vu32*)0x027FFFF8) = 0x06000000; // ARM7 Entry
(*(vu32*)0x027FFE24) = 0x06860000; // ARM9 Entry



Nothing like a "passme loop" here (see specs/ds_arm9vram_crt0.s, the assembly file that contains the initialization code for the stub) the ARM9 prepares the environment and enters "main()". Once the stub has loaded your requested .nds file (optionnally applying DLDI patching), the 'Execute()' function will play hide-and-seek with the ARM7, which is waiting for instructions through the FIFO hardware.

Le stub de DSchannel est un peu particulier en ce sens qu'il va squatter la mémoire vidéo pendant son exécution. Et celle-ci est tellement vaste par rapport à ce qui est nécessaire pour quelques messages texte que cela passera complètement inaperçu pour l'utilisateur. Pourtant, aussi bien le stub7, le stub9 que les arguments qu'ils reçoive (p.ex. le nom du fichier à lancer ;) tiennent dedans ^_^.

C'est le stub9 qui entre en jeu le premier, suite au 'soft reset'. le code d'initialisation (specs/ds_arm9vram_crt0.s) prépare à nouveau les cache, mais ne contient pas l'ombre d'une "boucle passme". Il démarre la fonction main() du stub qui à son tour, va charger l'exécutable NDS en mémoire (ajustant les drivers DLDI au passage, si nécessaire) avant de commencer à piloter l'ARM7 à travers quelques commandes via le FIFO hardware.
Celà signifie donc qu'au moins une commande 'EXEC' (qui ne fait rien de plus que de provoquer un reboot de l'ARM7 vers une adresse indiquée à l'avance par l'ARM9: celle du stub7) avant de faire une petite partie de 'cache-cache' entre les commandes FIFO et les valeurs de retour annoncées par une variable partagée ("synchro" à l'adresse 0x027FFFF8) jusqu'à ce que les deux moitiés de l'exécutable nds soient en place.

Another original thing here is that the original ARM7 code is still running while the ARM9 is already executing its own stub. Things will only change with the 'EXEC' command sent through the FIFO.
/* in stub_main9.c */
if(DebugEnabled) { a7(); iprintf("Booting...\n"); }
FIFO_Send(IPC_CMD_EXEC, 0);

/* in myapp_main7.c */
void IPC_Exec()
{
swiWaitForVBlank();

*(vu16*)(0x04000208) = 0; //REG_IME = IME_DISABLE;
*((vu32*)0x027FFE34) = *((vu32*)0x027FFFF8);

asm("swi 0x00"); //swiSoftReset();
asm("bx lr");
}

The ARM7 stub and the ARM9 stub then start playing hide-and-seek again, exchanging more commands through the FIFO and using a special "synchro" shared variable (0x27FFFF8) to report each other's progression.

mercredi, août 15, 2007

* * X F E R * *

Bon bon,.
C'est l'été, ça ne fait pas de doute. Et qui dit "été" chez les homebrewers dit "summer compo", ces évènements réguliers sponsorisés par les vendeurs de matos homebrew (neoflash, au hasard) et les grands forums (dev-fr.org?) où les codeurs proposent jeux et applications de leur meilleur crû avec l'espoir d'être promu heureux vainqueurs.

Bravo à Stravingo, d'ailleurs, qui décroche le 1er prix avec son application de météo ambulante à la dev-fr compo!

Summer ... sweet summer. Sunshine on beach girls and sunset on the ocean ... birds in the trees and homebrewers tidying their code for the competition deadline.

Desert tiles (Fury) loaded on the DS through WiFi!I haven't anything that could worth facing Stravingo's wheather report application (1st at dev-fr.org compo), but i'm polishing the building blocks of load/showME which -- even if not user-friendly at all -- now manage to receive binaries and PCX pictures beamed by WiFi and store them on the flash card.
The peak bandwdith observed was about 140-160 kbps during the beaming of CodenameHacker.nds ...

And with the code made available by eyeballkid, lick, chishm and grizzlyaddams, plus the joint efforts of wintermute, padrinator and sgstair on the devkitpro r20, i can even perform self-update of the application with a simple "SELECT" button press. That definitely boost development, dudes. I'll try to come with a comfortable application template with a self-update feature.


Moi, pendant ce temps-là, je bricole les derniers tests de "load/showME" qui - à défaut d'être convivial - parvient maintenant à stocker jeux et images proposés sur votre PC, à travers le WiFi ... et avec auto-update (par http) à la demande sur une simple pression de "SELECT". Testé avec succès sur le Codename Hacker .nds ^_^.

edit (17.08.07): roulement de tambours: non content de récupérer une version plus à jour, l'auto-update peut maintenant même redémarrer le logiciel lui-même, sans nécessiter de rebooter complètement la console! Le programme mis à jour est disponible en 5 secondes chrono (pour ~256KB) ! qui dit mieux ? je ne suis même pas sûr d'avoir le temps de recompiler ;-)

tout ça, grâce à un peu tous les grands noms (et les moins grands) mentionnés dans les posts précédents ... lick, chishm, wintermute, eyeballkid, padrinator, grizzlyadams etc.

lundi, août 13, 2007

"update homebrew via WiFi ..."

So, i finally successfully included Ben Campbell's "slurping" as part of my NDS WiFi attempt.
I guess it will save me hours in future development as i can now simply retrieve an update of the program as part of running the program itself.

Ideally, i'd have also include a reset of the software or at least a reset of the cartridge so that you're back at the .nds selection menu after the upgrade is complete, but this didn't worked as expected.

  • "Exec.cpp" from Bjoern Giesler's DSFTP works fine and allows me to transfer execution to another program. However, despite all my attempts, it seems that the started program cannot cannot access WiFi hardware when started in that way. This may not be a problem for a one-shot update of a non-WiFi related tool (like SEDS atm), but it certainly kills the idea of a debugging session free of DS power-cycling. (edit: maybe my code before exec() is missing Wifi_DisconnectAP(); ?)

  • libcartreset by Lick required a bit of source-tweaking (it assumed booleans in C :P) to compile, but it simply didn't work. Not the way i used it, at least, and i failed to find a decent tutorial on how to use it. All it does is halting the program, period. It may break any attempt to use the FAT library (or anything else that is cartridge-related) between the "cartSetMenuMode" and the "passmeloopEnter" calls, too.

  • Rebootlib by Lick is clearly something more complex. It supports more hardware, makes use of a DLDI-compliant "bootstrapping stub" that is patched at reboot-time against the running hardware. It is clearly designed to work with a more recent release of the DevkitPro than the one i have and required a couple of fixes on the registers name so that it almost compiles. Yet it expects some external _dldi_io symbol -- i guess this one would come from my own nds if it were DLDI-compliant -- which i cannot provide unfortunately.
All in one, it simply looks like it's time for me to drop my "working" release of devkitpro, upgrade to the latest one, find out how DLDI patches work and pray for my software to work flawlessly with that updated libfat ... Then screw the new devkitpro so that i can still use the "non-jumpy drawing" patch of padrinator

edit: installation of devkitpro r20 in progress ...
edit: good news #1 just found out the following comment in touch.c from the latest libnds:
$Log: touch.c,v $
Revision 1.19 2006/12/16 22:29:37 wntrmute
Incorporate Padrinator's touch code

edit: good news #2, in case everything else fails, there is "reboot.cpp" to be studied in tftpds-2.4beta

jeudi, août 09, 2007

Milestones for BilouDS

Hours spent in the bus between Austrian mountains, this was more than i needed to explore the future road of implementing Bilou platformer on the NintendoDS. Piece after piece, the jigsaw'd big picture is forming in my mind, combining properties edited through the sprite editor, things defined via C++ code and commands given through scripts (most likely given life through a WiFi connection).

I'm drawing inspiration from "Another World" setup, from my past experience with Clicker and WASP and from the PC Game Maker ...

Eh bien, ça en a fait, des heures de bus dans les montagnes Autrichiennes. Bien plus qu'il ne m'en fallait pour explorer les possibilités d'implémentation du jeu de plateformes 'Bilou' sur la DS. Pièce par pièce, le puzzle prend forme, combinant des éléments définis graphiquement dans le Sprite Editor, des éléments écrits en C++ et des commandes fournies à travers un script (transmis par Wifi). Je m'inspire du développement d'Another World, du Game Maker que j'ai utilisé sur PC et de mes expériences avec Clicker et WASP.
Je reparlerai de chaque points plus tard, mais au moins, comme ça, je ne perdrai plus la trame globale.

  • [next] record animations in SEDS: most of the "states" for sprites and character will be ruled by animations and we do not want to deal with that at code level.
  • [done] support WiFi scripting of the game scene: (up)load sprite tables, level maps, etc. and position objects from a remote keyboard ^_^
  • [ongoing] define hot-points and collision areas in SEDS
  • [done] provide basic game engine: state-machine processing, gravity, object-to-background collisions, etc.
  • [done] upload behaviour scripts via WiFi, including compilation and processing of transition rules.

I will likely come with more detailed motivation on each point ... but at least i won't have to try drawing the roadmap again and again ^_^

vendredi, août 03, 2007

Wifi again ?

Timide reprise de mon dévelopement homebrew ce matin ... j'ai mis en oeuvre la technique de "non-blocking IO" pour le projet "runME" (en fait, plutôt showME, pour l'instant) pour tester le transfer de fichiers par Wifi ...
Et comme je reviens à peine de vacances, je n'ai pas remis la main sur mon stick-lecteur-SD du bureau (oui, celui qui est tout pourri et qui a besoin d'une cale en papier pour permettre les transfers) donc je bosse à grand coup de DSFTP (qui marche une fois sur deux avec mon programme de 200K >_<)

int i=1
int tsock=socket(AF_INET,SOCK_STREAM,0);
ioctl(tsock,FIONBIO,&i); // set the socket non-blocking ...

struct sockaddr_in taddr = {
sin_family : AF_INET,
sin_port: htons(ports[id]),
};
taddr.sin_addr.s_addr=addr[id];

if (connect(tsock,(struct sockaddr*)&taddr,sizeof(taddr))==0) {
transfer->start(tsock);
iprintf("started transfer over socket %i\n",tsock);
} else {
iprintf("failed to connect (errcode=%i)\n",errno);
return;
}


Au vu du code dans sgIP_TCP_Connect, je serais bien tenté d'écrire un petit widget qui suivrait la machine d'état de TCP (port local défini, SYN émis, connecté ...).

Slowly restarting my DS/WiFi experiments ... I now implemented non-blocking I/O mechanisms that allow me to keep track of transfer progress with the GUI and interrupt transfer at anytime (rather than letting the thing stalls as with DSFTP :P)
Being back from holiday, i didn't have my card readers at hand for the first tests, and so i was forced to use DSFTP for updates -- which fails roughly every two uploads with my 200KB 'runME.nds' file.

Then my brother pointed me to Ds Slurper -- a tool that downloads file from a webserver on the DS flash card. It is an extremely simplified solution to the problem, e.g. it doesn't allow Access Point selection in case the firmware don't know your surroundings, nor does it allow you to pick anything that the URL stored in a config file on the flash card, but it works almost flawlessly with my SuperCard/SD cartridge. Huzzah.

I can already start dreaming of integrating EyeballKid's software into my stuff under the form of a "upgrade software via Wifi" button ^_^

edit+: petit test avec dslurper, comme prévu. Ca marche pas trop mal, même si le soft est simplifié à l'extrême ... pas de reboot software, pas de démarrage automatique du code obtenu. Et il sera nécessaire de transférer au préalable une URL sur la carte mémoire (c'est déjà mieux qu'un fichier complet, mais bon).
Bref, pris tout seul, l'intérêt est limité, mais avec les composants que j'ai déjà, ça devrait permettre un truc du genre "update homebrew software via WiFi", en particulier pour runME et les autres pour lesquels j'ai déjà presque tout ce qu'il faut ^_^