Pico-8 et la stéganographie

En parcourant un article de Scott Hanselman sur la console virtuelle Pico-8 ( https://www.hanselman.com/blog/ThePICO8VirtualFantasyConsoleIsAnIdealizedConstrainedModernDayGameMaker.aspx) , un paragraphe sur la cartouche de jeu à particulièrement attiré mon attention.

One of the many genius parts of the PICO-8 is that the “Cartridges” are actually PNG pictures of cartridges. Drink that in for a second. They save a screenshot of the game while the cart is running, then they hide the actual code in a steganographic process

Pour résumé, le screenshot du jeu est enregistré dans une image en même temps que le code binaire et l’état du jeu. Image et code sont donc mélangés pour ne former qu’une seule image sauvée au format PNG. Mais comment est-ce possible ?

Mixer Image et données

On va simplement utiliser une particularité de l’oeil humain qui a du mal à distinguer les teintes très proches. L’oeil humain est capable de distinguer 200 teintes différentes mais par sur tous les canaux de couleurs (en RGB) soit 8 millions de couleurs maximum pour une personne entraîné. Mais en réalité 300.000 couleurs différentes semble un chiffre plus réaliste (64 teintes par canaux). Pour plus d’informations vous pouvez consultez cet article très intéressant :

https://www.guide-gestion-des-couleurs.com/oeil-perception-couleurs.html

Maintenant que l’on connait les limite de l’oeil humain voyons quelles sont celle de la machine. Une pixel au format ARGB8 est capable d’afficher 16 millions de couleurs soit 256 teintes par canal (1 octet). C’est beaucoup plus que l’oeil humain n’est capable de percevoir.

Si l’on reprend les 64 teintes par canaux perçues par l’oeil humain cela correspond à 6 bits sur un octet. Il reste donc 2 bits sur chaque canal dont on peut se servir pour encoder des données. Bien sur on utilisera les 2 bits les plus bas (bit 1-0) pour ne pas que les données puissent influencer de manière significatives les teintes des pixels.

Les 2 bits les plus bas des canaux sont utilisés pour former une donnée d’un octet

En associant les canaux RGB et Alpha (A) (et oui cela fonctionne également sur l’opacité !) on dispose de 4 canaux dont les 2 bits peuvent former les parties d’un octet de donnée (4 * 2 bits = 8 bits = 1 octet). On peut donc considérer qu’un octet égale à une pixel.

Les images de cartouches de Pico-8 ont une dimension de 160×205 pixels. C’est à dire 32800 pixels soit 32K de données (code ou ressource). Impressionnant !

Les images sont au format PNG car il donne la possibilité de sauver des images de 16 millions de couleurs, compressés sans perte de données. C’est essentiel pour ne pas perdre les données mélangées à l’image. En revanche, le format JPG n’est pas adapté à notre usage car son format de compression, très performant, autorise, en contre partie, une perte de données sur l’image.

La cartouche Pico-8 du jeu “FEZ”
En zoomant la cartouche on peut voir des teintes légèrement différentes dans les couleurs (bleu notamment) signe de données cachés.

Le processus permettant de cacher des données dans un autre flux de données s’appelle la Stéganographie. Nous avons évoqué ici seulement la possibilité de cacher ses données dans une image mais potentiellement il est possible de le faire avec du son ou d’autres types de flux.

Algorithme simple de stéganographie

Si vous souhaitez coder votre propre intégration d’une stéganographie à la Pico-8 cela passera nécessairement par une bonne connaissance du binaire et de ses opérateurs.

Concrètement, on récupère les pixels d’une image auxquelles on applique un masque sur chaque canal pour que les deux derniers bits passent à zéro. On récupère ensuite 2 bits de données que l’on fusionnera avec le canal en cours. Pour un canal cela donne un code de ce type :

byte data = 0xFF; // la data à écrire

byte B6bits = B8bits & ~0x03; // on nettoie les 2 derniers bits du canal B (blue)
byte data2bits = data & 0x03; // on conserve les deux derniers bits de data
byte RData8bits = R6bits | data2bits;

data = data >> 2; // on prépare la data pour le prochain canal puis on passe au canal G (Green)

Librairie de stéganographie

Heureusement si vous n’êtes pas à l’aise avec ce type de développement bas-niveau vous pouvez utilisez des librairies déjà toutes faites.

Par exemple en C# : https://github.com/paw3lx/StegoCore qui vous permet d’écrire du code de ce type :

byte[] secretData = System.IO.File.ReadAllBytes("secret.data");
using(var stego = new Stego("someimage.jpg"))
{
    stego.SetSecretData(fileBytes);
    Image<Rgba32> secretImage = stego.Embed(AlgorithmEnum.Lsb);
}

Facile et intuitif !

Référence

Plus d’information sur le format de la cartouche Pico-8 ?

https://pico-8.fandom.com/wiki/P8PNGFileFormat

Une liste de cartouche PICO-8 jouables :

http://picoscope101.fr/

Rendre accessible un contrôle enfant en XAML

Lorsque l’on crée un UserControl en XAML (UWP ou WPF) il est parfois intéressant de laisser le développeur prendre la main sur les contrôle internes.

Dans l’exemple suivant le UserControl comporte un bouton.

<UserControl>
    <Grid>
        <Button x:Name="Button1"></Button>
    </Grid>
</UserControl>

Ce UserControl est ensuite instancié dans une page XAML :

<Page>
<local:ControlUtilisateur x:Name="ControlUtilisateur"/>
</Page>

On peut donc s’attendre à ce que le développeur de la page puisse appeler en C# le bouton Button1 à partir du UserControl de la manière suivante :

this.ControlUtilisateur.Button1.Content = "Hello";

Mais il n’en est rien.

En effet lorsque l’on crée un nouveau contrôle dans la page XAML à partir de l’éditeur et que celui-ci est nommé (x:Name=”Button1″), Visual Studio va générer automatiquement du code C# correspondant à ce bouton. Ce code généré est un raccourci qui nous évite de déclarer nos propres variables pour pouvoir accéder aux éléments XAML, ce qui est plutôt pratique.

/*exemple du code qu'il nous faudrait écrire (merci VS)*/
private Button Button1 = this.FindName("Button1") as Button;

Mais voila, le bouton “Button1” est généré en private par défaut. Il n’est donc pas accessible au delà de son parent, le UserControl “ControlUtilisateur”. Dans la plupart des cas, c’est le comportement attendu. Si l’on a besoin d’accéder à une propriété du Bouton, à partir du UserControl, on aura tendance à créer une propriété wrappant la propriété du bouton. Par exemple une propriété ButtonStyle dans le UserControl qui sera affectée à la propriété Style du bouton.

Mais parfois il peut arriver que l’accès complet au contrôle interne (Button1) soit nécessaire. Dans ce cas il existe deux solutions :

  • Créer une propriété de type Button nommée “Button1Bis” dans le contrôle et renvoyer l’instance de “Button1”. On doit avoir deux noms pour désigner le même contrôle :/
  • Ajouter une balise dans XAML permettant de changer l’accessibilité de l’objet “Button1” généré dans VisualStudio.

<UserControl x:Class="Blog.ControlUtilisateur">
    <Grid>
        <Button x:Name="Button1" x:FieldModifier="public"></Button>
    </Grid>
</UserControl>

Dans cette dernière solution “Button1” sera déclaré comme public et l’on pourra y accéder directement à partir de “ControlUtilisateur” mais seulement à partir du code behind C# (et pas de XAML).

Tous les modificateurs d’accès sont présents : public, protected, internal, private (par défaut) ce qui permet de définir un niveau d’accessibilité de votre contrôle enfant relatif au besoin des contrôles parents. Il semble intéressant par exemple d’éviter le modificateur public au profit d’internal/protected afin que le monde entier ne puisse pas accéder aux controles internes de vos UserControls.