Thursday, April 20, 2023

Retour chez les Minish

 

J'ai *enfin* remis la main sur ma cartouche de Minish Cap! C'était bien à la S-Team que je l'avais confiée, mais à Mlle A, et non pas à M. N (oui, ils ont grandi, maintenant). Du coup, pas besoin de prendre tout de suite un abonnement Nintendo+ avec ma switch et J.L.N a donc commencé un 4eme Zelda en parallèle. Il a pas mal avancé tout seul: tout le premier donjon. Là, il coince dans le village et insiste sur le fait qu'il *faut* qu'il puisse y devenir tout petit pour aller dans le Mont Gongle poursuivre sa quête.

J'ai donc relancé une partie moi aussi, parce qu'il y a une chose dont je me souviens bien dans ce jeu: les 'clés/portes' sont parfois loin d'être intuitives. Prenez ce tonneau, par exemple, qui renferme un item indispensable à notre progression. Lors de votre premier passage, il y a de fortes chances que des minish montent la garde devant chacune de ses entrées (et pas de chance, vous ne savez pas leur parler). Trouvez le minish qui connait la langue des humains, il vous dira d'aller dans le tonneau prendre ce qu'il faut pour vous faire comprendre. 

Oui, mais bon, c'est pas cohérent, là. "Comment j'explique au minish qui ne me comprend pas qu'il doit me laisser entrer dans la réserve pour prendre un truc pour qu'il me comprenne, hein, M. le Sage ?" que je voudrais lui dire, moi, au NPC. Logique non? Bin, ne te tracasse pas tant, mon grand: une fois ce dialogue passé avec le Sage, pouf, tout d'un coup, les minish qui montaient la garde sont partis casser la graine ou je ne sais quoi. Champ libre. Et des comme ça, il y en aura des tonnes (oh!).

Wednesday, April 19, 2023

Fury 2 ?


Je dois reconnaître que celle-là je ne m'y attendais pas, mais figurez-vous qu'une équipe s'est mise en tête de nous faire une reprise de Fury of the Furries! Eh oui. J'en veux pour preuve une vidéo "playthrough" du nouveau château. Bon, je suivais à distance le projet depuis un moment, et je ne peux évidemment pas m'empècher (après Giana et Bubsy) de juger "ce qui marche et ce qui ne marche pas" dans cette adaptation.

Tout bon: le pixel art. Sur les décors principalement, bien que les sprites ne soient pas en reste. Les graphistes semblent avoir bien capturé le côté "di-chromie" du premier opus et s'en servent judicieusement (mur du fond dans les tons mauves et boiseries qui ressortent bien) ainsi que les "textures" variées des briques et autres éléments de décors.

A améliorer: la bande son. N'allez pas me faire dire que l'artiste en charge ne fait pas de belles choses, mais sincèrement, comparé au travail d'El Mobo sur l'original (remixé?), on se sent loin du compte. Ça manque de punch et de personnalité. J'aurais presque envie de dire à CJ "allez, je leur laisse Pikaboo Castle" (mais il est déjà dans Wolfling ^^")

Know what? One of the authors of the original Fury of the Furries game has started an indie Furry 2 project! From times to times, we get videos of in-progress game development - and recently, that was a full playthrough video of one of the environments: the castle. Great pixel art, improvable soundtrack and a mood that feels like keeping the genome of the original game. Let's see whether we can say more about game design...

Interpellant: la longueur des niveaux. Fury, c'était un 'puzzle platformer'. Pas que la dimension "casse-tête" soit majoritaire comme dans un "Toki Tori", mais enchaînant des niveaux plutôt courts les uns derrière les autres. ça donne la possibilité de se concentrer sur l'exploration/l'observation d'un des niveaux pour trouver les mécanismes à activer, les passages secrets, etc. Ici, on a une map plutôt grande concentrant tout ce qu'il y a a dire (?) sur le chateau, et dans laquelle on nous invite à trouver un bon millier de fruits et légumes cachés. Pas loin de 40 minutes pour une exploration exhaustive sans respawn, c'est long, comme session de jeu.

Levels are much *much* longer than in the original puzzle-platformer game. That somehow make sense since the 'puzzle' part seems to have been dropped in favour of a more regular "explore-platformer with some local puzzles to get extra things". When you're designing a puzzle, you want to expose as much of the items to your player's eyes, so they brains can solve the puzzle instead of trying to remember what they've seen elsewhere. You also need to ensure failing the puzzle (which often implies dying or retrying from the start in the original game) won't ask your player to redo too much again, and short levels help on that. Still, here we've got about 40 minutes of video for a collect-all-items playthrough.

Mitigé: les pouvoirs. Fury, c'est 4 pouvoirs différents et (le plus souvent), la possibilité de passer de l'un à l'autre. Jaune = attaque, Rouge = mange-mur, bleu = nageur, vert = acrobate à la corde ninja. Le principe est conservé ici, bien que si je ne me trompe pas, il est maintenant possible de changer de pouvoir instantanément, sans attendre d'avoir mis son personnage à l'arrêt (ce dont je ne me plaindrai probablement pas). J'avoue que le côté " vert se balance avec sa langue" a un côté dégueu-kid-paddle dont je me serais passé, mais je suis prêt à le concéder comme liberté artistique des nouveaux (?) auteurs au même titre que je passe l'éponge sur l'exagération graphique de Rayman Origins/Legends. 

Bleu peut maintenant cracher de l'eau, ce qui donne lieu à quelques interactions sympas (je ne vais pas appeler ça des puzzles) comme ici avec un canon qui perd toute sa vigueur si on noie sa mèche. Il faut toujours Jaune et ses kameah pour se débarasser de la plupart des ennemis, donc ça reste équilibré et ça atténue un peu l'effet "Bleu ne sert à rien, y'a pas d'eau" du jeu original.

Rouge a maintenant la possibilité de voler en pétant. C'est kid qui va être ravi. La mécanique est rigolote, le contrôle en vol plutôt correctement dosé (à vérifier manette en main) entre trop fort et pénible-à-la-Donkey-sur-un-tonneau-fusée, l'habillage (comprenez les prouts) bien en phase avec le personnage (dévore tout et n'importe quoi) et la fonction (mon filleul parlait déjà des prouts de Yoshi pour justifier sa faculté à voler un petit moment dans Yoshi's Island). J'ai un peu peur que ça ne rende les acrobaties de Vert fort anecdotiques, par contre. Pourquoi se prendre la tête à faire le ninja quand on peut léviter ?

The general idea of 4 characters with different abilities and a way to swap between them (you only ever control one avatar) has been kept and the overall ability scheme has been preserved: Yellow shoots, Red eats, Green swing and Blue swims. Well, Blue can also exhale water (solves some puzzles, and used as a weak weapon). And green can slurp bonuses at a distance (possibly swallow small baddies ?). The most modified one is Red. He can now fart-fly for significant distances. To what extent will that break platforming or rope-swinging, I'm unsure of. I understand this won't disappear because it was the core gameplay of a mobile game I've never played. It doesn't look very balanced on the video, but maybe that's because player has already collected some fart-more-power-ups or something similar (I see hints that Yellow has now limited shots despite the counter never goes down, either). Anyway, since there are already portals that forbid the use of one character for one area, there's a clear way to enforce player would use rope-swinging or jumping in some specific room if level designer decides so.

Enfin, et c'est sans doute lié au passage du puzzle à une approche plus "collect-platformer", les retraits de pouvoirs semblent plutôt rares. Le jeu d'origine nous mettait souvent dans un niveau avec seulement 2 pouvoirs, et un 3eme à réactiver dans une zone précise. Ici, c'est plutôt "les 4 font la fête" en permanence avec quelques passages "Regarde Maman! Sans les mains!" pour l'une ou l'autre salle bonus.

Très bien: le côté décalé. On a gardé les loups-garous qui se changent en lapinou, les parodies de Dracula, les clins d'oeil aux flippeurs et compagnie. Trop tôt pour dire si les cameos seront aussi nombreux que dans l'original (en particulier dans le niveau final. On dirait bien), mais on a ni renié ses origines façon préhistorik-supernes, ni sombré dans un excès qui perd toute saveur façon film de Jim Carrey.

A tester: la barre de vie. Fury était un die-and-retry. Tout comme Commander Keen et Rick Dangerous, tout contact y était fatal. C'était précieux pour certains puzzles mais ça pouvait devenir franchement gonflant quand le moindre pet de mouche dans la tronche vous éclate. Là, avec 6 points de vie en début de niveau et des ennemis qui explosent au moindre tir (le jeu d'origine vous demandait régulièrement de bourrinner le bouton de tir 5 à 10 secondes pour la moindre araignée) et distribuent généreusement des petits coeurs, j'aurais du mal à fixer un pronostic.

One last item that might be worth tuning is the amount of hitpoints for both baddies and furries. The original game had a die-and-retry aspect for the player while any baddies would require numerous shots to be defeated. Here this is the opposite: plenty of hitpoints for the player and most ennemies explode in one shot, often releasing a heart for the player to recover. I know I've been wondering whether some god mode was enabled while watching the video. But it wouldn't be too hard to make that customizable by the player, adjusting heart-drop-probabilities or baddies resistance to Yellow's shots through menu or collectibles. Don't let that prevent you from wishlisting the game!

Bref, vous l'aurez compris: Fury 2 n'est pas un remake de Fury 1, mais il reste fidèle à l'esprit et à l'ambiance de l'original. J'aurais envie de comparer l'évolution de gameplay à ce que l'on a pu voir entre Ori 1 et Ori 2. Je souhaite une bonne finalisation au projet et un grand merci au Cyrille qui bosse là-dessus de nous proposer un retour en Furries. Wishlistez bien ^_^

Tuesday, April 18, 2023

std::unique_ptr<>

Okay, it shouldn't be that hard to have a variable that gives access to an object RAII-style while allowing late initialization: I should use std::unique_ptr

RAII is neat. Despite having an impossible-to-use-and-remember-name (don't be surprised if I use 'Rabi' instead ;), it means that you spend less time catching/rethrowing exceptions, less time checking return codes (because you used exceptions) and can think of your software as something as reliable as a building instead of some Jenga game. Want to have some code dealing with some file, but don't want to forget closing the file handle if anything goes wrong ? Simple.

  • have a RabiFile class that encapsulate your file handler
  • have the ctor of RabiFile do *everything* that's needed to be working on the file
  • have the destructor always going to a clean state.

As soon as you've written something like RabiFile music("forest.xm", RabiFile::RO), you're good to go

  • either your RabiFile exists and all its methods are now valid
  • or you failed to create it and an exception has been thrown, taking you out

So with that approach, you'll never write any music->open("underwater.xm") nor any music->close() anywhere. That makes writing of constructors a *bit* more complicated, I admit, especially those for objects that contains some RabiObjects: they have to catch exceptions and rollback any 'personal' resource acquisition they performed because nothing invokes destructor on objects that haven't been fully constructed. If your constructor fails with an exception, it's up to your constructor to ensure it leaves no mines behind.

But that usually don't happens a lot. It does happen when I stretch the RAII fashion to long-lived objects like GameLevel that has LevelMap and SpriteSet(s), or to GobState that has GobTransitions, though. But Rabi* is mostly for temporary things that need us to hold something while something else.

There's one common drawback to both, though. Because my objects have to be allocated on the stack to benefit automatic cleanup on failure, it is tricky to benefit from polymorphism. Say I should either create an instance that reads the script from a file or an instance that reads it from a buffer received over WiFi by runME ... I can't just replace BufferReader reader(...) at the head of my function by


loaderCode() {
   if (fromFile) {
       FileReader reader(whichFile);
   } else {
       BufferReader reader(whichBuffer);
   }
   DoStuffWith(reader); // no such reader, dude.
}

because there, the condition-dependant objects are no longer valid when I then want to use them. And migrating the DoStuffWith into the condition will soon turn unpleasant as well because it is not DRY. So instead, I can have


loaderCode() {
   std::unique_ptr<InputReader> reader;
   if (fromFile) { 
      reader = std::unique_ptr<FileReader>(new FileReader(...));
   } else {
      reader = std::unique_ptr<BufferReader>(new BufferReader(...));
   }
   DoStuffWith(*reader);
}

Granted, that will not allocate the object on the stack but at least it guarantees that the created object gets deleted whatever happens during DoStuffWit(*reader). Given the size of the NDS stack, it might not be a bad move.
Maybe I could have done it with auto_ptr instead. It seems it was the way to do it before C++11 came, declared auto_ptr obsolete and unique_ptr as being the way to do this instead.

A few things to remember when working with unique_ptr

  • you must use `std::move(reader)` if you intend to return the unique pointer as function value
  • you must use std::move(aswell) if you want to capture some unique pointer you've received as a member of a new uniquely pointed thing

  •  

Wednesday, April 12, 2023

StyleHax on DSi

For (too?) long, I've staid aside of DSi homebrew. Partly because there's little the camera could offer to my projects and I don't really need more than 4MiB of RAM, but mostly because until recently, running homebrew on that device felt like an arm race with Nintendo. You need to break into the machine through exploits the same way you'd take control of someone else's server on Internet and that has a taste of dark side I'm not fan of. Plus, once you've managed to get into things, you then have to install more permanent modification if you want to be sure to be able to do the same the next day, since Nintendo could ban the vulnerable software you've used to gain access (if you were lucky enough to have it on your device on first place).

But one post on hackaday might well change all this. Nathan Farlow has found a way to use the web browser shipped with the DSi firmware to execute arbitrary code.

 He just had to get his code into the right spot. For this he employed what’s known as a NOP sled; basically a long list of commands that do nothing, which if jumped into, will slide into his exploit code. In modern browsers a good way to allocate a chunk of memory and fill it would be a Float32Array, but since this is a 2008 browser, a smattering of RGBA canvases will do.

Ultimately, that will execute a boot.nds file from the SD card, such as a homebrew launcher.

Octobre dernier, j'avais jeté un oeil un peu plus attentif à "Comment on peut faire tourner des homebrews sur DSi". S'en était suivi un graphe compliqué dans mon calepin indiquant quel menu dépend de quel installateur qui dépend de quel firmware custom. Mais au final, on finissait toujours par dépendre d'un "hax" pour prendre le contrôle de la console. Un vrai piratage en règle, quoi, et qui dépend en général de la disponibilité d'un logiciel vulnérable sur sa console.

Pendant toute la durée de vie de la bête, Nintendo a mené une chasse farouche à ce genre de vulnérabilité, supprimant jeux et applications du service DSiWare au fur et à mesure qu'elles étaient identifiées, étendant sa liste de cartouches "nds" interdites (dont les clés avaient été cassées et utilisées pour des linkers) lors de mises à jour système indispensables pour faire tourner les nouveaux jeux ou refaire un téléchargement sur le DSiWare. J'étais resté à l'écart de tout ça. Mais j'ai commencé à réviser ma position: un nouvel "exploit" (comprenez "mécanisme permettant de faire tourner son code à soi en exploitant une vulnérabilité) à été trouvée, et pour le navigateur intégré, tout simplement!

It should be trivial to try: install your favourite DSi-capable software as boot.nds on an SD card, direct your DSi browser to nathan's web page and voilà. Except nothing guarantees the contents of Nathan's page will stay the same over time, so I'd rather download that locally and study the stylehax source code before I start using that on my own device. 

At the start of the chain is an html document with some NDS binary embedded as "shellcode" in the html <script> part, plus some javascript to exploit the use-after-free bug of the browser. The payload.c featured in the stylehax repository contains actually just a little loop to inject the 'true payload' at some dedicated location (held as a blob into payload.h). And that true payload comes from another repository (well, actually, that second repository contains many exploits, but the one we'll use is the 'minitw' one, according to payload.h)

As soon as the 'payload.c' part is running, we already have access to most resources of the DSi. The goal of minitwlpayload is to setup the proper context for the default bootloader of the devkitpro project embedded into the .nds (yes, that comes from a third repository :P) The bootloader will mostly run on the ARM7 core, which is the only one to have access to SD card registers on the DSi. There will be many passme-loop-synchronization-points until we're ready to execute the NDS or DSi code contained in boot.nds, one for each ARM9 function we want to execute between two ARM7 functions.

Pour celui qui veut tenter sa chance, rien de plus simple! L'auteur de l'exploit a mis en ligne une page HTML (une seule) intégrant le code nécessaire pour aller exécuter le fichier boot.nds qu'on trouverait sur la carte SD de la DSi et le javascript qu'il faut pour faire bugger le navigateur au point qu'il se mette à exécuter un morceau de page web canvas HTML5 comme si c'était du code machine. Et surprise, c'est du code machine, cette fois-ci! Enfoncé, HTML Quest ;-)

One question remains, though: how does ARM9 exploit trigger execution of custom code on the ARM7 ? It looks like the answer lies within the following snippet of exploit.c:

    memcpy16(VRAM_C, decrbegin, decrsize); // writing custom code into VRAM at 0x6000000
	VRAM_C_CR = VRAM_ENABLE | VRAM_C_ARM7_0x06000000;
    // now inserting a jump at the start of ARM7-dedicated memory area.
	*(vu32*)0x2380000 = 0xE51FF004; // ldr pc, =0x06000000
	*(vu32*)0x2380004 = 0x06000000; // (constant for above)
	debug_color(0,15,0); // dark green
	IPC_SendSync(1); // MAGIC!

For some reason, when we trigger an IPC IRQ on the ARM7 with code 1 (out of 16 possible codes), it executes code from the start of the ARM7 area again, where we have installed our trampoline to VRAM. Why does it behave so ? I guess the reason lies in some line of code for the ARM7 code in the TWL sdk, sealed in a vault at Kyoto ...  I presume I'll have to accept it without further understanding.

Seulement moi, j'ai envie de comprendre un minimum ce qui se passe. Histoire entre-autre d'éviter que la page ne télécharge un jetaibieneu.nds au lieu de démarrer hbmenu.nds rebaptisé boot.nds, par exemple ?Et aussi pour enfin comprendre cette histoire de .twl et donner une meilleure chance à l'exécution de mes homebrews sur DSi. Donc je parcours les pages github, ma liseuse dans une main, mon calepin dans l'autre...

exomizer/exodecrunch

One notable action of exploit.c from minitwlpayload is to "decrunch" the embedded bootloader into VRAM where it will execute, that is, to undo the compression performed by the exomizer tool invoked by dsi/exploits/minitwlpayload/Makefile.

  • it packs the 'bootloader' from devkitpro github from 6KiB into a bit more than 4KiB. Unsure whether it was critical to use in this context :-P
  • ndsload.bin is just an 'objcopy' binary-extraction of the ndsload.elf
  • https://bitbucket.org/magli143/exomizer/src/master/src/Makefile could be the source for the exomizer tool, but I fail to get why it would need a 6502 emulator ... (I guess this is linked to the 'self-extracting for C64, Apple II and BBC micro' feature)

Possibly, such steps could make sense for larger payloads or for other exploits where the amount of data we can embed is limited (remember, there has even been exploits involving QR code readers)