lucidiot's cybrecluster

Extraire des activités pour Microsoft Train Simulator avec Kaitai Struct

Lucidiot Informatique 2022-06-12
Mon tout premier projet officiellement publié pour Windows 2000 et XP : la réécriture d'un utilitaire d'un jeu de Microsoft Games.


Il y a plus d'une dizaine d'années, quand tout le monde utilisait soit Windows XP, soit Windows Vista, j'allais parfois dans le rayon de jeux vidéos PC au supermarché. J'y avais trouvé un jeu nommé Microsoft Train Simulator qui promettait une simulation super réaliste de conduite de train. J'aime les trains depuis fort longtemps et globalement je joue beaucoup à des simulateurs en tous genres, y compris de nos jours, donc j'étais tout content et j'ai pu convaincre mes parents de me l'acheter.

J'ai installé le jeu avec les deux CD sur l'ordinateur familial sous XP, je l'ai lancé, j'ai essayé de conduire un train et je n'ai rien compris du tout. Il n'y a de base quasiment aucun affichage tête haute, aucun guide, et contrairement à d'autres simulateurs du genre comme ceux de la franchise Trainz, il n'y a aucune version simplifiée des contrôles. J'étais perdu, et j'ai laissé tomber pour la journée. Je suis retourné dans ce jeu le lendemain, en suivant cette fois-ci d'abord le « voyage de présentation », une session où l'ordinateur conduit le train tandis qu'une voix off explique comment contrôler la caméra et présente le jeu, puis les quelques didacticiels où la voix off demande qu'on appuie sur des touches pour apprendre à contrôler les trains. Bizarrement, après avoir appris à jouer, tout est devenu plus facile.

J'ai rapidement découvert qu'il existe beaucoup de contenu supplémentaire sur Internet, créé par la communauté et que je pouvais télécharger gratuitement et ajouter au jeu. J'ai notamment découvert ça avec des vidéos YouTube créées avec Windows Movie Maker, comportant un watermark www.Bandicam.com ou Unregistered HyperCam 2 ; le gage d'une vidéo de grande qualité. Le ChrisTrains JetTrain, un train fictif composé de réacteurs d'avion monté sur bogies et pouvant approcher Mach 1, est lancé à pleine vitesse et déraille, s'envolant dans les airs pendant quelques secondes avant que le jeu n'indique Interruption de l'activité - Une voiture a déraillé !.

J'ai installé le JetTrain, et je n'ai aucune idée du nombre de fois où je me suis amusé à le faire dérailler de toutes les façons possibles et imaginables. J'ai aussi joué avec moult autres créations de la communauté ; je me souviens notamment avoir utilisé une rame MP89CA de la ligne 14 du métro parisien sur les lignes du tramway de Bordeaux. J'ai vite compris que MSTS, sorti en 2001 et compatible avec Windows 95, est un des tout premiers simulateurs de conduite de train et un des plus populaires. La communauté est assez vaste, y compris en France, et est encore active en 2022.

La popularité de ce jeu est telle qu'il existe désormais OpenRails, un autre simulateur open-source en C# qui maintient la compatibilité totale avec les formats de fichiers de MSTS pour profiter de son riche contenu et ainsi fournir une version moderne de MSTS.

J'ai continué à jouer à MSTS, ainsi qu'à d'autres simulateurs de conduite de train, pendant des années. J'ai expérimenté avec des modes multijoueur, notamment sur Trainz 2010: Engineer's Edition, et j'ai essayé de créer mes propres cartes, trains ou scénarios. Cependant, après mon passage sous Linux, j'ai quasiment arrêté de jouer à la plupart de ces jeux ; c'est à la fois une combinaison de flemme de configurer Wine, de manque de temps puisque j'étais pris par mes études, et surtout du fait que je préférais jouer à Euro Truck Simulator 2.

Puisque j'ai récemment recommencé à jouer avec Windows 98, 2000 et XP, j'ai voulu ressortir mes jeux sur CD et les installer. Je me suis donc remis à jouer à MSTS. J'ai aussi testé OpenRails, ce que je n'avais jamais fait auparavant, sous Windows 7.

Je me suis cependant heurté à un problème que je n'avais jamais vraiment rencontré auparavant, tout simplement parce que je ne m'y étais jamais vraiment approché quand j'étais plus jeune. J'installais des nouvelles cartes, souvent appelées routes parce que le dossier du jeu qui les contient s'appelle ROUTES, ainsi que des nouveaux trains, mais je n'ai jamais installé des activités indépendantes.

Les activités sont des scénarios de conduite ; par exemple, la route Innsbruck - St. Anton, une des 6 routes de base du jeu, fournit une activité Meurtre dans l'Orient Express, où Hercule Poirot vous affiche des messages vous demandant de vous arrêter, repartir, ou dételer des voitures tandis que son enquête avance. Il est possible de partager avec les autres des activités seules, sans fournir la carte ou les trains, pour fournir des fichiers assez légers.

Installer les activités des autres

Pour rendre ce partage plus simple et sans prise de tête pour un utilisateur, les développeurs du jeu ont réinventé la roue en fournissant un format de fichier spécial pour ces activités, permettant l'installation dans les dossiers du jeu en quelques clics. Ce format s'appelle Packaged Activity, activitée empaquetée, et utilise l'extension de fichier .apk. C'est la même extension que pour les paquets d'applications Android, qui sont apparus plusieurs années plus tard et qui n'ont rien à voir. Dans cet article, « APK » désignera les activités empaquetées et jamais les applications Android.

Le jeu fournit divers utilitaires pour aider à l'édition de cartes, locomotives, wagons, convois (ensembles prédéfinis de locomotives et wagons), ou activités, et parmi ces utilitaires se trouve TSUnpack.exe. Cet utilitaire permet d'extraire un fichier APK pour installer une activité dans le jeu. L'installation de Microsoft Train Simulator ne semble pas ajouter tout seul l'extension APK aux extensions de fichier connues de Windows, donc la plupart des fichiers Lisez-moi des activités téléchargées sur Internet inclueront des instructions pour glisser-déposer le fichier APK vers TSUnpack afin que l'utilitaire ouvre cette archive et l'installe.

TSUnpack a beau être un utilitaire très simple, avec pour seule interface utilisateur une boîte de dialogue d'erreur quand on n'a pas de chance, il a cependant divers problèmes. D'abord, on ne peut pas choisir d'extraire où on veut facilement ; l'utilitaire utile la valeur du Registre HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Games\Train Simulator\1.0\Path pour déterminer le chemin d'installation du jeu et utilisera ça pour l'extraction. Ensuite, si des fichiers existent déjà, ils ne seront jamais écrasés : l'extraction échouera à la place en indiquant qu'un fichier existe déjà. L'extraction aura cependant déjà commencé, donc on aura extrait une activité seulement à moitié. On ne peut donc pas utiliser ce système pour installer des mises à jour par exemple.

J'ai aussi essayé de configurer TSUnpack comme l'extracteur pour les fichiers APK, en ouvrant le fichier APK et en utilisant la boîte de dialogue Ouvrir avec pour sélectionner TSUnpack. Ça n'a pas fonctionné, et c'est peut-être là aussi pourquoi l'extracteur n'était pas associé par l'installeur automatiquement. Cela améliorerait pourtant assez bien son expérience d'utilisation.

Sachant que beaucoup d'activités sont partagées en ligne, que la grande majorité utilise le format APK, et qu'il semblerait que certaines ne puissent jamais être extraites avec TSUnpack, je me suis dit qu'il existait forcément des alternatives. J'en ai effectivement trouvé quelques unes. Certaines ne sont plus téléchargeables et ne sont pas non plus sur la Wayback Machine puisqu'elles étaient sur des pages nécessitant un compte sur des forums. D'autres sont encore disponibles, mais l'inscription sur ces forums est difficile car il faut vraiment aimer les trains américains et s'y connaître pour pouvoir ne serait-ce que résoudre le CAPTCHA personnalisé du site ainsi que répondre aux questions qui seront soumises à l'administrateur pour approbation.

Je n'ai finalement pas réussi à trouver d'alternative simple à TSUnpack, et je me suis mis à plutôt essayer de comprendre comment le format APK marche, pour voir si je ne pourrais pas faire quelque chose moi-même.

Le format Packaged Activity

Je suis tombé dans mes recherches sur une application Ruby on Rails qui semble vraiment être le seul outil open-source concernant les fichiers APK, apk2zip. Cette application permet de convertir un fichier APK en fichier ZIP, et comprend donc un lecteur de fichier APK. Il n'est pas du tout documenté mais il est assez court, ce qui laisse penser que ce format est plutôt simple. J'ai également pu ensuite faire quelques déductions en ouvrant moult activités avec HxD pour en lire les données binaires directement.

D'abord et avant tout, les fichiers APK sont des archives au format gzip. On peut donc les décompresser assez facilement, par exemple avec WinRAR, pour révéler leur véritable structure. Je vais commencer par expliquer son organisation logique avant de rentrer dans les détails de son encodage :

En Nombre de fichiers Fichier 1 Fichier 2 ... te

On a un en-tête, composé de quelques champs que je décrirai ci-après, suivi d'un entier 32 bits non signé indiquant le nombre de fichiers, suivi de chacun des fichiers.

En : Nom de route Identifiant de route Identifiant de route 4 octets te

L'en-tête se compose de trois chaînes de caractères : le nom de la route lisible pour les humains, et deux fois l'identifiant de la route, qui est en fait le nom concret du sous-dossier de la route : la route par défaut du jeu appelée Corridor du Nord-Est se trouve dans Train Simulator\ROUTES\USA1 et son identifiant est donc USA1. Je ne sais pas pourquoi l'identifiant est répété deux fois ; des expérimentations ont montré que l'ensemble de l'en-tête n'est en fait d'aucune importance à TSUnpack.

Après ces trois chaînes de caractères se trouvent 4 octets dont je n'ai non plus pas la moindre idée de leur utilisation. Tout ce que je sais, c'est qu'ils n'ont aucun impact sur l'extraction de toutes les activités que j'ai pu rencontrer.

Une fois qu'on a pu passer sur cet en-tête fort peu utile, on atteint le nombre de fichiers, qui nous indique simplement combien de fois on doit s'attendre à voir se répéter la structure qui décrit un fichier :

Fichier : Taille Chemin relatif Contenu

Un fichier est décrit par trois champs : d'abord, on a sa taille en octets, un nombre entier 32-bits non signé. On a ensuite une chaîne de caractères indiquant son chemin depuis la racine de l'installation de Train Simulator.

Toutes les activités contiennent au moins un fichier .act qui contient les métadonnées de l'activité, le briefing de mission affiché au joueur, les horaires à respecter, les wagons à atteler ou dételer, la configuration des aiguillages, les limitations temporaires de vitesse, etc. ; on aurait donc pour ce fichier un chemin relatif tel que routes\USA1\activities\activity_name.act. Le chemin est souvent en minuscules, sauf pour l'identifiant de route, probablement parce que le générateur de fichiers APK doit se fichier complètement des majuscules.

Enfin, on a le contenu du fichier ; des données binaires, qu'on ne doit interpréter autrement qu'un ensemble d'octets sans signification, dont la longueur est définie par la taille qu'on a lu auparavant. On notera que la taille ne tient donc pas compte des quelques octets occupés par le chemin relatif du fichier.

Encodage

On a donc trois types de champs : des entiers 32 bits non signés, des chaînes de caractères et des octets quelconques. L'extracteur de apk2zip utilise pour toutes ses lectures binaires un mode low-endian, ou petit-boutisme. On avait déjà vu le boutisme un peu plus en détail en traitant le Well-Known Binary ; les octets sont arrangés dans un sens qui est plus simples pour les machines, mais plus compliqués pour les humains. Cela rend la compréhension du format un peu plus difficile. Voici par exemple la chaîne de caractères USA1 :

05 00 00 00 55 00 53 00 41 00 31 00 00 00 5 U S A 1 caractè res

Dans tous les formats d'encodage de texte que je connais, le caractère dont l'octet ou les octets ne sont que des zéros est appelé le caractère nul. Un bon nombre de langages utilisent des caractères nuls pour terminer leurs chaînes de caractères. J'avais vu des chaînes de caractères qui n'ont que la longueur et pas de caractère nul, et des chaînes de caractères délimitées par des caractères nuls, mais pas les deux en même temps. Notre chaîne de 4 caractères est donc indiquée comme en ayant 5, parce que le dernier est le caractère nul.

Un détail des formats de fichiers spécifiques à Windows, et qui porte à confusion pour beaucoup de personnes peu familières avec la programmation sous Windows, est que Windows a d'abord implémenté le support de Unicode en utilisant UTF-16 avec un petit-boutisme (UTF-16LE). C'est le cas pour des raisons historiques : Windows a ajouté le support Unicode assez tôt, suffisamment tôt pour ne pas pouvoir observer le consensus dans les autres implémentations pour s'aligner ; Microsoft a donc dû faire un choix parmi plusieurs méthodes d'encodage disponibles pour Unicode.

Les versions plus anciennes de Windows NT utilisent UCS-2, un encodage qui force tous les caractères à n'être que sur deux octets et rien d'autre, mais il est ensuite devenu apparent que 65536 caractères ne seraient pas suffisant pour encoder l'intégralité d'Unicode. Les encodages UTF-16 et UCS-4 ont alors été introduits : UCS-4 force tous les caractères à occuper 4 octets, ce qui demande beaucoup d'espace disque et peut réduire les performances, et UTF-16 encode les 65535 premiers caractères sur 2 octets, et tous les autres sur 4 octets. Windows, qui utilisait déjà UCS-2 et avait donc déjà des caractères à 2 octets, a choisi UTF-16, pour avoir simplement un cas particulier où les 2 octets en deviennent 4. En Occident, les caractères après U+FFFF ne sont pas utilisés fréquemment (à une époque où les emojis, en U+1Fxxx, n'existaient pas encore), donc on observe le plus souvent des caractères à 2 octets.

C'est à partir de Windows XP que UTF-8, l'encodage utilisant 1, 2, 3 ou 4 octets et qui est aujourd'hui adopté par la majorité des implémentations, est pris en charge, mais tout le fonctionnement interne de Windows, tous les formats comme les fichiers exécutables, les partitions NTFS, etc. utilisent UTF-16LE. Ce n'est qu'à partir de 2019 que Microsoft commece à intégrer un véritable support pour UTF-8, et décourage désormais l'utilisation d'UTF-16.

UTF-8, l'encodage majoritaire de nos jours, garantit une compatiblité avec ASCII, qui n'utilisait que 7 bits : les 127 premiers caractères sont encodés sur un seul octet dans les deux encodages. Ce sont les caractères les plus fréquemment utilisés en anglais, et les 128 caractères suivants contiennent la majorité des caractères utilisés dans les langues d'Europe de l'Ouest, y compris le français. On est donc totalement habitués à voir des caractères n'occupant qu'un seul octet.

Tout cela mène donc à une confusion générale face à cette chaîne de caractères. Non seulement on a un petit-boutisme, mais en plus, si on essaie de lire octet par octet, on voit des caractères nuls partout ! Cette chaîne de caractères a l'air complètement cassée.

Ce n'est cependant pas du tout le cas ; le premier caractère, U, est le caractère Unicode U+0055 LATIN CAPITAL LETTER U. On est en UTF-16LE, donc il y a deux octets pour ce caractère, et en petit-boutisme il sera donc écrit 55 00. De façon générale, du texte en alphabet latin envahi d'octets à zéro comme ceci signale automatiquement de l'UTF-16 ou de l'UCS-2. Notre chaîne de caractères ne s'arrête donc véritablement qu'avec le caractère nul représenté par deux octets à zéro consécutifs.

tsunpack-csharp

Avec cette description du format APK, nous avons tout ce qu'il nous faut pour commencer à écrire quelque chose qui peut décompresser un fichier APK, en lire le contenu, et effectuer une extraction.

Mes objectifs, avec ce nouvel extracteur, sont simples :

Je ne veux en fait pas d'une interface graphique, je ne veux pas même une barre de progression, je veux juste fournir ce que le TSUnpack original est censé faire, de façon plus robute, et facile à utiliser pour n'importe qui, tout en laissant la possibilité en ligne de commande de faire des choses un peu plus bizarres.

J'ai décidé de faire cette réécriture en C#, puisque je connais ce langage, puisque presque tout le monde dispose déjà du .NET Framework sous Windows, et puisque Kaitai Struct le prend en charge. Évidemment, on parle de format binaire, il y a forcément un moment où je sors mon jouet favori. Puisque je suis développeur et que je n'ai aucune compétence en marketing, ma réécriture de TSUnpack en C# s'appelle donc... tsunpack-csharp.

Schéma Kaitai Struct

On va écrire un schéma pour la structure décompressée des fichiers APK, puisqu'on ne peut pas directement décompresser dans un schéma Kaitai Struct. On peut commencer par écrire l'en-tête :

meta:
  id: msts_apk
  title: Uncompressed Packaged Activity
  application: Microsoft Train Simulator
  file-extension: apk
  license: AGPL-3.0
  encoding: utf-16
  endian: le

J'ai appelé ce format Uncompressed Packaged Activity pour rendre l'état décompressé plus clair. J'indique que MSTS est l'application principale pour ce format, que l'extension la plus commune est .apk, et que le schéma est sous licence AGPL 3.0. Et comme on l'a vu précédemment, j'indique que tout le fichier utilise du petit-boutisme et que l'encodage du texte est en UTF-16, pour mettre tout ça derrière nous.

On va ensuite devoir définir trois structures : la chaîne de caractères, le fichier, et la structure principale de l'APK. On va commencer par la chaîne de caractères. Kaitai dispose bien d'un type str, mais il n'est pas capable de comprendre automatiquement la présence d'une longueur ni de détecter un caractère nul en UTF-16.

types:
  apk_string:
    -webide-representation: '{value}'
    seq:
      - id: len
        type: u4
      - id: value
        type: str
        size: (len - 1) * 2
      - id: terminator
        type: u2

Je commence par un attribut len, la longueur de la chaîne. value est la chaîne de caractères elle-même, et sa taille est assez étrange : J'enlève 1 au nombre de caractères indiqué dans len, pour ignorer le caractère nul, et je multiplie ensuite cette longueur par deux car Kaitai s'attend à un nombre d'octets, par de caractères d'un encodage particulier. Pour 5 caractères, on ira donc lire 8 octets.

J'ajoute ensuite un champ ayant le splendide nom de terminator, qui contiendra le caractère nul tout seul. Ce caractère nul pourrait autrement complexifier un peu la lecture du fichier dans notre code C# ; on devrait constamment retirer tous les caractères nuls en fin de chaînes pour pouvoir les exploiter facilement. Je ne sais pas si ce caractère nul, qu'on ignorera donc complètement, partira à la recherche de Sarah Connor.

Notez la propriété -webide-representation. Son préfixe par un tiret, repris des propriétés CSS officieuses comme -moz-force-broken-image: 1, indique que cette propriété n'est pas définie officiellement par Kaitai, et qu'elle devrait donc être ignorée par des applications traitant le schéma Kaitai Struct et ne savant pas quoi en faire. Cette propriété est utilisé par l'IDE en ligne de Kaitai pour afficher une représentation de cette structure sous forme d'un titre. Pour nous aider à tester notre fichier, l'IDE nous laisse ouvrir des fichiers binaires pour les lire avec notre schéma, et une arborescence donne la structure qu'on a interprété ; le titre d'une structure apk_string dans l'arborescence sera donc défini à la valeur de la chaîne de caractères, ce qui est plus pratique.

Maintenant qu'on a la chaîne de caractère, on peut enchaîner avec une structure décrivant un seul fichier contenu dans un APK.

types:
  # ...
  file:
    -webide-representation: '{name}'
    seq:
      - id: file_len
        type: u4
      - id: name
        type: apk_string
      - id: content
        size: file_len

On commence par la taille du fichier, file_len, un nombre entier à 4 octets non signé, puis on utilise une chaîne de caractères pour obtenir son nom. Enfin, on récupère le contenu du fichier : on n'indique aucun type, ce qui fait que Kaitai va interpréter ça seulement comme un tableau d'octets quelconques, et on récupérera le nombre d'octets indiqué par le champ de taille du fichier.

Il est à noter que apk2zip, ainsi que des versions initiales de tsunpack-csharp, traitent le contenu du fichier comme une chaîne de caractères UTF16-LE et ne permettent donc pas l'extraction de données binaires. La version 1.0 de tsunpack-csharp autorise n'importe quelle donnée binaire, pour un maximum de compatibilité, mais cela montre que la majorité du contenu d'un fichier APK n'est que du texte, puisque la plupart des utilisateurs n'ont rien remarqué.

On peut enfin assembler toutes ces structures pour former le fichier complet :

seq:
  - id: route_display_name
    type: apk_string
  - id: route_id1
    type: apk_string
  - id: route_id2
    type: apk_string
  - id: unknown_number
    type: u4
    doc: I do not know what this value is and I can work without it!
  - id: file_count
    type: u4
  - id: files
    type: file
    repeat: expr
    repeat-expr: file_count

On commence par nos trois chaînes de caractères pour le nom et l'identifiant de la route, puis on ajoute notre numéro inconnu. Pour un peu de complétude, j'ai mis un commentaire expliquant ce nombre inconnu. On ajoute ensuite le nombre de fichiers, et on répète la structure de fichier autant de fois que le nombre de fichiers indiqué. Il n'y a pas beaucoup de magie.

Vous pouvez consulter la version complète de ce schéma telle qu'elle est incluse dans le projet.

Structure du projet

Le projet tsunpack-csharp se constitue en fait d'une solution Visual Studio 2005 avec quatre projets :

Le tout est configuré pour utiliser le .NET Framework 2.0, la version la plus récente du .NET Framework que Windows 2000 peut prendre en charge ; vous pouvez donc utiliser tsunpack-csharp sur Windows 2000 ou Windows 11 sans problèmes.

Installation

Lorsque je publie une nouvelle version, elle apparaît dans la page des releases. J'y publie le programme d'installation, appelé TSUnpackSetup.msi. Vous pouvez télécharger ce programme et l'exécuter pour lancer l'installation, ce qui ajoutera automatiquement une nouvelle association de nom de fichier pour APK. Vous pouvez à tout moment désinstaller TSUnpack sans risques, ce qui retire cette association de nom de fichier.

Utilisation

Vous pouvez utiliser cette nouvelle version de TSUnpack exactement comme la version originale, ou vous pouvez profiter de ses améliorations. Double-cliquer sur un APK l'extrait automatiquement dans le dossier du jeu, tel qu'il est indiqué dans le registre Windows. Vous pouvez également utiliser l'utilitaire en ligne de commande, ce qui vous donne accès à bien plus de fonctionnalités :

Usage: TSUnpack.exe [-f|--force|/f|/force] [-d|--destination|/d|/destination destination] path [path...]
Unpacks one or more Microsoft Train Simulator Packaged Activity files.
If any of the files do not exist or if any error occurs while parsing one of the files, an error is printed and the file is skipped.

-d, --destination
  When unset, the destination directory will be retrieved from the Path value of the Windows registry key at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Games\Train Simulator\1.0.
  If the destination path does not exist or the registry key is not found, the program will throw an error.

-f, --force
  Overwrite existing files when unpacking.
  By default, the program stops if a file already exists.

Lorsque vous double-cliquez sur un fichier, la commande qui s'exécute est TSUnpack.exe <chemin du fichier>. Cette commande simple extrait un APK directement dans le dossier du jeu. Mais vous pouvez aussi spécifier plusieurs APK simultanément, et ils seront tous extraits les uns après les autres. Si une des extractions échoue, une erreur est indiquée et l'extraction des autres fichiers continue.

Vous pouvez utiliser l'argument -f ou --force, qui permet d'écraser des fichiers existants ; sinon, par défaut, un fichier qui existerait déjà causera une erreur. Vous pouvez aussi l'écrire /f ou /force, puisque les arguments utilisent des tirets sous Unix et des barres obliques sous Windows.

L'argument -d ou --destination prend en paramètre un dossier de destination, pour vous permettre d'extraire dans un dossier autre que celui du jeu. Par exemple, TSUnpack.exe /d . activity1.apk activity2.apk extraiera deux activités dans le dossier courant.

Conclusion

Grâce à cet utilitaire, le tout premier projet sous Windows XP que je publie officiellement dans une version stable, j'ai pu augmenter mon jeu d'un grand nombre de scénarios supplémentaires sans avoir à télécharger de nouvelles cartes ou de nouveaux trains, et je peux maintenant m'amuser à conduire des trains sous Windows XP, quand je ne suis pas en train d'y faire de la rétro-ingénierie...

Je travaille sur quelques autres petits outils pour me faciliter la vie sous XP, mais ils sont un peu plus longs à développer et je n'ai pas forcément eu autant de motivation à les faire, donc on les abordera plus tard. Il y a beaucoup d'autres explorations que j'ai faites, que je suis en train de faire ou que je veux faire et qui méritent un peu de documentation...


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !