Thursday, March 30, 2023

NDS Dev in 202x

Back in february, 19-yo Violet! posted an animated version of the low-poly DS collected 167K likes and over 380 comments, most of which going towards "my childhood" nostalgia. Clearly, the NDS is now a retro console. That may not bring a wave of NDS development the same way we saw people starting to do SNES development a decade ago, but that's enough to push some 23-y-o 3D artist to convert their model into a .nds just to check what it'd look like and animate their hair with more code.

I've been surpised to hear the snes-lady Skwirl reporting that she had first wanted to write homebrew herself back in the days but dropped it because it was "too complicated". (she started some 3DS port of tilemap town, btw)

There are some ongoing NDS projects on github. I spotted one that pushes the 'reimplement X on NDS' to the extreme, bringing support for GBA pokemons, no less. Codestudy started. Probably tricky to get into it. And given how 'mature' the device is now, chances are that we find more ambitious projects like this (or a minecraft clone or cave story port) than the tiny one-screen flash/arcade-inspired titles of the neocompo years.

Curieusement, on dirait que ça ce ravive un peu, du côté du dévelopement amateur NDS, ces derniers temps. On est encore loin de l'age d'or, mais voir une Violet de 19 ans poster un modèle 3D de DS lite et récolter 380 commentaires parlant de "c'est toute mon enfance" et 167 mille likes, ça pourrait bien indiquer que la console est finalement devenue rétro, après tout. Un autre, de 23 ans, à carrément décider d'exporter le modèle 3D de son personnage fétiche en une ROM pour animer sa chevelure. Tout ça m'a remis en mémoire un commentaire plus ancien qui demandait "comment on fait un homebrew NDS, au fait". Et je dois reconnaître qu'une grande partie des tutos et ressources de l'époque ne sont plus à jour: les procédures ont évolué, les bibliothèques aussi ...

Resources
It reminded me of a series of tweet where someone was wondering how to get into NDS development. I have a tutorial git of my own, where each commit presents a part of what I use to build games on NDS, but that's teaching you to use libgeds, not libnds. There are devkitpro examples, too. I can't really recall whether their coverage is convincing for someone who wants to kick in. I myself have learnt with a mix of that and open-sourced projects from fellow homebrewers, but these are likely obsolete and wouldn't build against recent devkit versions.

The closest I've found is magusti tutorials. Most 'readmes' are in spanish, unfortunately and the directory structure is a bit confusing. It uses modern libraries, including maxmod. It uses 'all your data are belong to 4MB of RAM' approach, though.

Mais quelque petites recherches github ont quand même donné quelques résultats intéressants. Si la plupart des projets actifs utilisant libnds sont des portages plutôt ambitieux (et trop gros pour pouvoir servir de tutoriels), si la NFlib a le défaut de contenir encore pas mal de commentaires en Espagnol, on trouve quand-même deux ou trois choses directement exploitable, comme la balle-qui-rebondit de Magusti ou une adaptation de '2048'.

Maybe the best way would be to use NFLib ? Like in 2048-nds, a small puzzle game with a codestudy-firendly size ? It does restrict what you can do but usually in ways that won't hurt your project (like assume you don't want to use 16-colors layers), it has understandable wrapper functions and meaningful abstractions like auto-adjust position of sprites when scrolling background planes or delay OAM updates until vertical blanking happens. It let you chose between "my files are in fat:/SOME_DIR" or "my fat are in magical NitroFS (within the .nds, but requires compatible loader).

If that's fine for you, the projects Snake-DS seems like a nice thing to check first.

Getting Started

The windows installer 3.0.3 is still there, and it seemed to install things properly on a Windows 10 machine. Or so it seemed. Likely the Antivirus quarantining 'padbin.exe' during the installation process did no good, but all I actually get (apparently) is an msys2 install in C:\devkitpro\msys2 (which hasn't messed with the other msys2 pre-installed on that system. yahoo!) and that's it.

I did get a 'choose your packages' box, but it did not produce anything apparently. Well, pacman -Sl dkp-libs did indeed report what's available and pacman -S libfat-nds libnds nds-examples was all I needed to get the devkitarm (now r59) and the make rules as well. Some dependencies were still missing to build NDS examples though, namely ndstool (and I also missed dswifi because it's not named ndswifi ^^"). That's not yet enough. I'm still missing 'default.elf' from the default-arm7 pacakge :-/

So make your life simpler than mine: read the doc to the bottom and pacman -S nds-dev to install the pre-selection instead ^^"

Pour la forme, j'ai même voulu vérifier que tout ça compilait bien avec la version la plus récente des outils devkitpro. Eh bien, ça marche, et contrairement à ce que j'avais cru plus tôt dans l'année, il y a toujours bien un installateur pour windows (version 3.0.3). Bon, on ne s'emballe pas, il se contente essentiellement de nous filer un msys avec un gestionnaire de packages modifié. Je découvre d'ailleurs en fin de parcours la présence d'un méta-package 'nds-dev' qui pré-sélectionne tout ce que j'ai personnellement téléchargé à la main ^^"

Edit: looks like there's even someone who's busy designing a R4 substitute based on dual-core Raspberry Pico ...

Sunday, March 26, 2023

3 rooms on 3DS

Novembre 2021, un rétro-fan nommé Fei m'invite à rejoindre un chat francophone qui accueille déjà un certain nombre de mes contacts twitter versés dans le rétro-coding. J'y ai fait quelques apparitions, mais je dois bien admettre que j'ai du mal avec la formule 'discord' qui a tendance à semer la confusion dans mon esprit.

Du coup, quand j'ai une nouvelle petite démo avec les 3-salles, bin je leur poste le lien, pour avoir un peu des retours. Sauf qu'à l'époque, la plupart des dev' qui ont un matériel compatible avec les homebrews NDS ont en réalité une DSi ou une 3DS... et là, bardaf: écran bleu.

J'en arrive assez rapidement à la conclusion que le fautif est le support des vieux linkers utilisant le port GBA (qui est absent des DSi et 3DS). Les tentatives de lectures sur la plage d'adresse prévue pour le "gba slot" provoquant des exceptions hardware. Mais même une fois ce code-là commenté, toujours pas moyen de faire tourner la démo. En fait, le code n'a pas trouvé les fichiers de données "intégrés" au .nds avec l'Embedded File System de Noda. Fei utilise le "Twilight Menu++" ... je creuse un peu mais fin de semaine, je n'ai toujours rien. Perso, ma 3DS n'a encore jamais vu de homebrew de sa vie (malgré l'acquisition d'un OOT3D) et j'ai oublié comment je faisais tourner des homebrews sur ma DSi avant que ses triggers ne me lachent. Le tourbillon du printemps 2022 va emporter tout ça au pays d'Oz dans l'oubli.

My bad. Really. It would not have taken me one whole year figuring out why my homebrew wasn't running on DSi and 3DS if I had not been messing with the ndstool settings in an attempt to avoid 'twl' headers. I started studying how any exploit on DSi seems to ultimately rely on something called 'generictwlpayload' to launch devkitpro/bootloader. But chances are that if it does not find some specific signature in the binary, it will fail to inject hooks to e.g. SD card reading.

Anyway, this is *so* satisfying to finally see someone else (Fei, this time) being able to run my demos again. I hope I will find a way to make it compatible with desmume as well and quickly manage to backport that 'fix' to some SchoolRush build... just in case.

With --slot1 R4 --slot1-fat-dir /tmp/fat
/tmp/fat/Dream.nds
, it now works.

Mais là, ce week-end, pour voir comment je peux m'y prendre pour faire des tapis roulants, je me rends compte que mon mercurial sur sourceforge n'est pas à jour. de plusieurs mois ^^". En regardant de plus près je retrouve une micro-branche qui reprend mes tentatives pour corriger le problème. La différence, c'est qu'entre temps, j'ai été démarché pour une version "cartouche" de SchoolRush. Et que j'ai commencé à essayer de comprendre comment faire tourner des homebrews DSi sans linker, encouragé par un post sur hackaday qui promet un mécanisme ne nécessitant ni jeu particulier ni modification permanente. Juste un p'tit serveur web et le navigateur intégré au firmware. (j'ai encore un peu de codestudy à faire dessus, mais on y reviendra).

Tout ça pour dire que j'avais une idée bien plus précise de "twl = indispensable pour démarrer quelque-chose sur DSi" qu'il y a 4 ans quand j'ai fait la mise à jour de mon devkit. A l'époque, j'avais d'abord remarqué un gonflement de tous mes programmes, puis une incompatibilité avec desmume. Le coeur du problème, c'était un changement de fonctionnement de l'outil ndstool mais en voulant "forcer" le support de desmume, j'avais aussi sabordé le support des consoles DSi et 3DS. Une petite marche arrière et Fei est maintenant en mesure de faire tourner la démo "three rooms" sur sa 3DS ^_^.

Voyons donc si je peux aider desmume à comprendre ces "nouveaux headers" si chers au TWLoader, sans lesquels il n'essaie même pas de faire le patch DLDI des softs qu'il charge... encore que ... en 2016, je pointais que desmume n'avait plus besoin de --gbaslot-rom pour faire tourner SchoolRush ... à creuser.

edit: 3/4: With some GameInfo::isHomebrew() function located, I thought it wouldn't be too hard to allow dsi-enabled .nds to run with desmume, but that was not the problem (not with 0.9.14, at least). The problem was more with DLDI patching. Unfortunately, the handlers checked by slot1_GetCurrentType() are not yet registered into slot1_list[] when you invoke desmume --slot1 R4 Dreams.nds. Adding slot1_Init(); slot2_Init(); at the start of CommandLine::process_addonCommands() definitely helped.

Edit: 8/4: For some reason, slot1 is super-slow in the emulator. I'd like to try slot2 instead, but the GameInfo structure is full of zeroes since NDS_LoadROM() has been called. A preloadROM function finally allowed using --gbaslot-rom and have it recognized as a homebrew, but that still did not allowed libfat to find files.

Monday, March 20, 2023

qmake (CMake and 'friends' pour une autre fois)

j'ai mis un tag 'tutoriel', mais considérez ceci comme juste un moyen pour moi de ne pas oublier les griffes que le chat m'a fait pendant que j'essayais d'apprendre à me servir d'un nouvel outil

Bon, des Makefiles, ça va faire plus de 20 ans que j'en mange. C'est plus ou moins la base des règles de compilation. Vous indiquez un nom de fichier à obtenir, puis les fichiers dont il dépend, puis les commandes à exécuter pour passer de l'un à l'autre.


Monfichier.o: Monfichier.c FichierImportant.h
	compiler Monfichier.c -o Monfichier.o
     
Et bien sûr, vous avez le droit de rendre ça plus générique:

%.o: %.c FichierImportant.h
	compiler $< -o $@

Bon, assez rapidement, ça devient plus complexe que ça, hein. Par exemple, il y a peu de chance que tout votre projet dépende de FichierImportant.h et seulement de ça. On risque plus d'avoir quelque-chose comme jeu.c qui dépend de jeu.h et sprite.h alors que sprite.c ne dépend que de sprite.h et menu.c dépend à la fois de menu.h sauvegardes.h et sprite.h, par exemple.

Si on a de la chance, le compilateur offre alors une option pour générer des fichiers de dépendances qui capturent ça (disons, jeu.dep, menu.dep et sprite.dep) et on s'en sort avec


%.o: %.c
	compiler $< -o $@ $(ET_GENERER_LES_DEPENDANCES)

-include *.dep

Mais restons-en là pour l'instant. Dès que le projet grossit, qu'il commence à dépendre de bibliothèque externes (pour les formats d'images, la gestion du son, etc.), et surtout, dès qu'on commence à vouloir le compiler pour plusieurs plate-formes différentes (linux et windows?), ça devient vite un beau gros sac de noeuds ... en particulier parce que si make est omniprésent dans le monde Linux, il est toujours boudé par Microsoft qui y va de son propre msbuild.exe qui manipule des fichiers XML déguisés en .vcxproj ... D'où l'intérêt de programmes qui vont générer des Makefiles à partir de descriptions plus haut-niveau du projet à compiler.

C'est le cas de qmake, notamment, qu'on va exécuter dans un répertoire vide en lui indiquant l'emplacement de nos sources et qui va créer un Makefile dédié à la compilation de nos sources dans ce répertoire-là en utilisant le fichier *.pro distribué avec les sources.


  mkdir build
  cd build
  qmake ../src
  
  • il y définit tous les programmes utilisés (compilateur, linkeur, etc.) dans des variables pour make;
  • il donne une règle indiquant quand re-générer le Makefile (est-ce que le fichier .pro a changé?) qui réappellera qmake;
  • il ajoute les règles pour préparer une distribution du programme, nettoyer les fichiers intermédiaires, etc.
  • et surtout, parce que qmake est lié au projet d'interface graphique Qt, il prend en charge tout ce qui concerne la génération de code pour l'interface graphique à l'aide de moc.

A travers son fichier .pro, qmake prend en charge le fait que les différents compilateurs ont des préférences différentes pour les macro-définitions (à ajouter dans DEFINES) ou les répertoires à explorer quand il tombe sur un #include (à ajouter dans INCLUDEPATH). Idem avec les bibliothèques à passer au linkeur. On trouve évidemment aussi SOURCES pour les fichiers à compiler et HEADERS pour les déclaration de classes contenant des annotations pour le système de signal/slot propre à Qt (qui en aura besoin pour générer du code et compléter les vtables).

Une autre grande différence entre make et qmake est que ce dernier se veut multi-plate-formes et indépendant du shell. ça se traduit notamment par une manière franchement confortable pour les éléments conditionnels, comme


linux {
    LIBS += pthread
    !isEmpty(ENABLE_GL) {
       SOURCES += backend/opengl.cpp
    }
}
windows {
    LIBS += direct3D
}

Et autres astuces du genre. Attention par contre: si il était possible de faire des Makefile qui incluent d'autre makefiles (notamment pour combiner les résultats de plusieurs répertoires), la chose qui s'en rapproche le plus avec qmake c'est le TEMPLATE = subdirs, mais qui à l'instar de makefiles récursifs, ne partage pas les variables d'un fichier .pro à l'autre. Oui, je sais, en lisant ça, ça semble évident, mais ça l'était nettement moins en écrivant le makefile. La solution, c'est de faire venir la variable de l'extérieur soit avec qmake ENABLE_GL=y sur la ligne de commande, soit avec Qt Creator


 Une approche pas toujours très confortable, cela dit, en particulier à cause d'une gestion des dépendances entre variables et fichiers .o pas toujours très claire ... je comprends pourquoi mes collègues lui ont préféré include (../projet.pri) dans les .pro-feuilles et qui contient les DEFINES += ENABLE_GL=yes et autre MODEL_EXPORTER_PATH=/usr/bin/export_to_gl

Friday, March 17, 2023

Conveyed ...

It's not a surprise (or it shouldn't) if there are waterfalls and sandfalls in the Three-rooms demo. And especially, if there is ground between two of them. I want my game engine to be able to 'push' you when you're on some ground, like it would if the ground was actually a conveyer belt. And it's been a few years now that using sand/water falls for that integrates better than mere mechanical belts in most levels. 

I have notes in my notebook for quite some times, too, about how the game script should indicate what those blocks do. They're not quite "special blocks" (that interact with collision code), but rather "physical types" that could be polled by frame-per-frame controllers.

I'm not completely satisfied with the proposed syntax, though. I'd rather use type %d {...} and is %s "%x" for level editor identification (like for block %d {} describing interactive special blocks), and I'd use props %x, like we already have for special blocks.

The truly innovative part is %s.%s = ... pattern to setup variables into controller factories. That one will be a bit more complex to get running, because the map of factories is actually managed directly by the GobState class, and I'd rather not re-inforce the Using... anti-pattern here.