
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.
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).
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).
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.
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...
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!
Friday, August 24, 2007
The Armaggedon Machine
Tags: designclass, english, game, keen, keys and locks, level design, nostalgy
Thursday, August 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 ^_^
Tags: thisblog
Thursday, August 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 */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.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");
}
Wednesday, August 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.
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.
Monday, August 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.
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
Thursday, August 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 ^_^
Friday, August 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 ^_^

Vote for your favourite post
