DEV Community

Cover image for Hexegesis : projet de modding des jeux FromSoftware en Haskell
Karim DRIDI
Karim DRIDI

Posted on

Hexegesis : projet de modding des jeux FromSoftware en Haskell

← Revenir au sommaire de la série

Hexegesis est un projet personnel consacré au modding des jeux FromSoftware en Haskell. J’ai envie d’y construire progressivement une boîte à outils composée de petits programmes en ligne de commande : inspecter les fichiers d’un jeu, identifier leurs formats, extraire des ressources, les transformer et les réintégrer.

Le point de départ reste très concret. Je voudrais remplacer une texture, modifier une police de caractères ou changer un écran d’accueil, puis vérifier le résultat dans RPCS3. Chaque objectif fournit un problème précis à résoudre et détermine les capacités dont j’ai réellement besoin. Plutôt que de chercher à prendre en charge intégralement un format, je commence par la plus petite partie utile.

Cette série me sert de journal de bord. J’y rassemble les problèmes rencontrés, les documents qui m’ont aidé, les choix d’implémentation et les limites de ce que j’ai effectivement vérifié. Je ne connais pas encore toutes les étapes nécessaires, et je ne souhaite pas reconstruire après coup une progression artificielle : les résultats obtenus détermineront la suite.

Ce préambule présente le projet, les raisons qui me poussent à le développer et la manière dont les articles seront organisés.

Sommaire

Une première modification visible dans RPCS3

Je souhaite commencer par une modification dont le résultat se constate immédiatement. Une nouvelle texture sur une arme ou un vêtement de départ, une autre police de caractères ou un écran d’accueil modifié conviendraient tous à cette première étape. Le choix définitif dépendra surtout de la chaîne de formats qu’il faudra réellement traiter.

RPCS3 peut charger une image du jeu ou une version extraite sous forme de répertoires. Pour appliquer les premiers mods, je travaillerai sur cette arborescence extraite : les fichiers modifiés pourront y être ajoutés ou remplacés sans reconstruire immédiatement une image ISO.

Une première expérience consistera à appliquer à mon exemplaire un mod déjà publié. Le but ne sera pas de republier son contenu, mais d’établir un résultat de référence : relever les fichiers concernés, identifier les outils employés, appliquer la modification, puis vérifier son effet dans RPCS3. Une fois cette procédure comprise, je pourrai reprendre chaque opération et déterminer comment produire moi-même une modification de même nature.

La chaîne de travail devrait ressembler à ceci :

obtenir l’arborescence du jeu
→ localiser le fichier concerné
→ identifier ses éventuels conteneurs
→ extraire la ressource recherchée
→ la convertir vers une représentation modifiable
→ effectuer la modification
→ reconstruire les conteneurs nécessaires
→ replacer les fichiers dans l’arborescence
→ vérifier le résultat dans RPCS3
Enter fullscreen mode Exit fullscreen mode

Cette liste ne décrit pas encore une procédure validée. Je connais le résultat recherché, mais pas tous les formats intermédiaires ni toutes les contraintes de reconstruction. Les premières manipulations serviront précisément à les déterminer.

↑ Retour au sommaire

L’ambition d’une BusyBox du modding

Dans un premier temps, j’utiliserai les outils existants pour avancer, comprendre la chaîne de traitement et établir les résultats attendus. Un script Python, un programme spécialisé ou une manipulation manuelle peut parfaitement servir de référence provisoire.

À terme, j’aimerais cependant qu’Hexegesis permette de réaliser l’ensemble de ce travail sans dépendre d’une collection disparate de programmes. J’ai davantage une boussole qu’un plan arrêté : mon ambition serait qu’Hexegesis devienne, à son échelle, une sorte de BusyBox du modding. La comparaison place la barre assez haut, mais elle décrit bien la direction recherchée.

BusyBox réunit de nombreux utilitaires spécialisés derrière un même programme. Hexegesis suivrait un principe semblable dans un domaine plus restreint : sonder une image, parcourir une arborescence, trouver un fichier, l’extraire, reconnaître son format, l’exporter vers une représentation usuelle, importer une modification et reconstruire les données nécessaires.

Chaque commande devra accomplir une opération précise. Elle devra posséder des entrées, une sortie, des erreurs et des limites explicites. Les commandes pourront être utilisées seules ou composées depuis le Shell ; leur logique restera également accessible depuis une bibliothèque Haskell.

L’existence préalable d’un outil ne retire donc pas automatiquement une opération du périmètre d’Hexegesis. Cet outil peut d’abord me permettre d’avancer et de vérifier mon résultat. Son remplacement ultérieur par une implémentation Haskell répondra à un autre objectif : obtenir une boîte à outils cohérente, documentée et suffisante pour les formats effectivement pris en charge.

La lecture seule constitue le premier périmètre. Lorsqu’une opération d’écriture deviendra nécessaire, elle devra produire un nouveau fichier ou une nouvelle arborescence plutôt que modifier silencieusement la source. Les règles précises seront définies et testées à partir du premier besoin réel d’import ou de reconstruction.

↑ Retour au sommaire

Pourquoi Demon’s Souls sur PlayStation 3 ?

Il fallait choisir un premier jeu parmi des milliers. J’ai choisi la version PlayStation 3 de Demon’s Souls pour une raison simple : j’aime ce jeu. Je préfère consacrer du temps à des fichiers que j’ai réellement envie de comprendre et pouvoir lancer régulièrement le jeu sans que la vérification devienne une corvée.

Je collectionne les jeux vidéo depuis l’enfance et j’en possède aujourd’hui plus d’un millier. Les jeux FromSoftware occupent une place particulière dans cette collection. J’apprécie aussi la manière dont les mods permettent de redécouvrir un jeu connu, parfois plusieurs années après sa sortie. Le modding réunit ainsi deux activités qui m’intéressent déjà séparément : programmer et jouer.

Je possède au moins deux versions différentes de Demon’s Souls, Dark Souls et Dark Souls II. Elles pourront plus tard servir de corpus comparatif. Une structure observée dans une version ne sera cependant pas attribuée aux autres sans vérification : une édition régionale, une révision ou une plateforme différente peut employer une organisation distincte.

Demon’s Souls offre enfin un autre sujet qui m’intéresse depuis longtemps. De nombreux articles consacrés au data mining évoquent les ressources associées au royaume des géants et à la sixième Archstone, abandonnée dans le jeu publié. Ces articles rapportent les conclusions de personnes qui ont examiné les fichiers, mais ils donnent rarement les moyens de mesurer directement ce qui subsiste.

J’aimerais pouvoir le vérifier moi-même. S’agit-il de quelques fichiers isolés autour desquels les interprétations se sont accumulées ? D’une zone déjà largement construite mais privée de certaines ressources ? Quels éléments sont directement observables et quelles conclusions demandent une extrapolation ? Je ne veux pas supposer la réponse. Je veux disposer des outils nécessaires pour examiner les données et délimiter ce qu’elles permettent réellement d’affirmer.

Cette étude viendra plus tard. Avant d’analyser un contenu abandonné, il faut savoir atteindre les ressources, reconnaître leurs formats et les lire correctement.

↑ Retour au sommaire

Pourquoi Haskell ?

La première raison est personnelle : j’aime Haskell et j’ai envie de mieux le maîtriser. Le projet ne cherche pas à démontrer qu’il serait le meilleur langage pour la rétro-ingénierie ou le modding. De nombreux langages et outils savent déjà traiter des formats binaires. Je choisis Haskell parce que c’est dans ce langage que je souhaite réfléchir à ces problèmes.

J’ai commencé à programmer avec de l’assembleur. J’ai ensuite pratiqué le VHDL et l’assembleur sur Game Boy, puis je me suis progressivement tourné vers les langages fortement typés et fonctionnels, notamment OCaml et Haskell. Ces deux parcours ne me paraissent pas contradictoires. Le bas niveau oblige à comprendre la représentation effective des données ; les types et les abstractions permettent ensuite d’exprimer cette compréhension sans la laisser dispersée dans le programme.

Après une trentaine d’années de programmation, je prends de plus en plus de plaisir à reprendre des opérations simples et à chercher la formulation qui les rend à la fois explicites et composables. Lire quatre octets, calculer un offset ou décoder un entier ne présente aucune difficulté algorithmique particulière. La question intéressante devient : où placer la connaissance de la largeur du champ ? Comment représenter un résultat inconnu ou invalide ? Comment séparer la description d’une structure de l’effet qui consiste à lire un fichier ? Comment réutiliser une même description pour décoder, annoter ou documenter ?

Le programme produit m’intéresse, mais la manière de l’écrire m’intéresse tout autant. Je souhaite trouver une syntaxe dans laquelle l’intention reste visible et où les contraintes du format ne sont pas disséminées entre plusieurs couches de code.

Les types algébriques permettent de nommer les différents résultats au lieu de les réduire à des valeurs conventionnelles. Les fonctions pures permettent d’étudier séparément la transformation des octets et leur acquisition. L’écriture monadique permet d’enchaîner les calculs tout en rendant explicites les effets qu’ils requièrent. Cette séparation entre la valeur recherchée et les effets nécessaires à son obtention correspond particulièrement bien à la manière dont j’ai envie d’aborder les parseurs.

J’ai déjà expérimenté des architectures fondées sur les GADT, les approches tagless final ou les monades libres. Je connais encore peu certaines bibliothèques d’effets comme effectful, mais j’y pressens des outils susceptibles de m’intéresser. Hexegesis me fournit une raison concrète de les étudier, de les comparer et surtout de constater ce qu’elles apportent sur un programme réel.

Ces techniques ne seront pas introduites pour donner au projet une apparence sophistiquée. Elles apparaîtront si un besoin les justifie. Une abstraction pourra être essayée, mesurée sur plusieurs formats, puis conservée, simplifiée ou abandonnée. Le journal de bord devra rendre compte de cette décision au lieu de présenter après coup l’architecture retenue comme une évidence.

Le modding est donc à la fois une finalité et un domaine d’application pour mon apprentissage de Haskell. Inversement, Haskell me fournit les outils conceptuels avec lesquels je souhaite formaliser le travail de modding. Aucun des deux sujets n’est un simple décor pour l’autre.

↑ Retour au sommaire

Du besoin à l’implémentation minimale

Les informations utiles au modding sont souvent dispersées entre des forums, des dépôts de code, des scripts, des outils spécialisés et des documents techniques volumineux. Beaucoup de ces ressources ont été écrites pour résoudre efficacement un problème précis. Leur contexte, leurs hypothèses et les raisons d’un offset particulier ne sont donc pas toujours explicités.

Je souhaite produire le type de document qui me manque lorsque j’aborde un nouveau format : un objectif concret, les passages utiles des sources, les observations correspondantes, le raisonnement qui les relie et une implémentation dont le périmètre est annoncé.

Il ne s’agit pas de lire intégralement une spécification avant d’agir. Dans le premier article, la norme ISO 9660 publiée sous la référence ECMA-119 contient bien davantage d’informations que nécessaire pour retrouver le nom du volume, sa taille logique ou le marqueur Joliet. Je consulte les sections qui répondent à la question en cours, tout en conservant la référence complète afin de savoir où poursuivre si un besoin ultérieur l’exige.

Pour chaque capacité, je suivrai autant que possible le même ordre de travail :

  1. formuler un résultat observable ;
  2. examiner les outils et les implémentations existants ;
  3. retrouver les documents qui décrivent les structures nécessaires ;
  4. effectuer une lecture petite, précise, bornée et strictement en lecture seule ;
  5. interpréter les valeurs et consigner leur provenance ;
  6. distinguer les faits établis, les observations locales, les inférences et les questions restantes ;
  7. écrire la plus petite implémentation utile au résultat ;
  8. construire des données synthétiques pour tester les cas valides et les erreurs ;
  9. confronter séparément l’implémentation au corpus réel ;
  10. annoncer exactement les capacités obtenues et leurs limites.

Cette discipline est connue sous le nom de design par soustraction. Fumito Ueda l’a mise en œuvre dans la conception d’Ico en retirant les éléments qui ne servaient pas son idée centrale. David Sirlin, présent lors de la conférence donnée par Ueda à la Game Developers Conference de 2004, en propose une explication détaillée à partir de cet exemple.

La référence trouve naturellement sa place dans ce projet : Hidetaka Miyazaki a expliqué que la découverte d’Ico lui avait révélé les possibilités du jeu vidéo et donné envie d’en créer lui-même. Cette décision l’a conduit à rejoindre FromSoftware, où il a ensuite dirigé Demon’s Souls.

Appliqué à Hexegesis, le principe reste concret. Lorsqu’une capacité ne contribue pas au résultat actuel, je préfère l’écarter plutôt que la développer sur la base d’un usage hypothétique. Une abstraction n’est introduite qu’après l’apparition d’une répétition ou d’une difficulté réelle. Sa confrontation à un second format permet ensuite de décider si elle doit être conservée, réduite ou remplacée.

Cette méthode n’oppose pas la pratique à la théorie. Une observation sans modèle reste difficile à généraliser ; une description théorique qui n’est jamais confrontée aux données ne suffit pas davantage. Les commandes Shell, les spécifications, les types Haskell et les tests apportent des formes de preuve différentes qui doivent rester reliées sans être confondues.

Une norme définit un format, mais ne prouve pas qu’une image particulière lui est entièrement conforme. Deux implémentations indépendantes peuvent corroborer une structure sans devenir une spécification officielle. Une valeur lue dans mon exemplaire reste une observation locale. Une fixture synthétique prouve qu’un test couvre le cas qu’elle représente, pas que le programme accepte toutes les variantes rencontrées dans le monde réel.

↑ Retour au sommaire

Écrire pour apprendre

Cette série est d’abord écrite pour moi. Les problèmes de modding qui m’intéressent sont difficiles à reprendre plusieurs mois plus tard lorsque les informations restent réparties entre mon historique Shell, quelques notes et des dépôts externes. Un article m’oblige à reconstruire le raisonnement de manière assez précise pour pouvoir m’en servir de nouveau.

J’ai travaillé une dizaine d’années comme formateur à Epitech et je suis aujourd’hui consultant en intelligence artificielle. Cette expérience a renforcé une conviction simple : transmettre quelque chose reste pour moi l’un des meilleurs moyens de l’apprendre. Expliquer une structure oblige à vérifier son vocabulaire ; publier un exemple oblige à retrouver sa source ; écrire un test oblige à préciser le comportement attendu.

Je ne présente donc pas un programme d’enseignement dont je maîtriserais déjà toutes les étapes. Je partage un apprentissage en cours. Certains articles documenteront une réussite directe ; d’autres conserveront une erreur ou une hypothèse abandonnée lorsqu’elle aide réellement à comprendre la solution.

Je ne souhaite pas non plus réduire artificiellement les sujets difficiles. Une notion complexe peut être présentée simplement sans employer un synonyme approximatif ni dissimuler sa véritable définition. Le rôle du texte est d’introduire le terme exact, de fournir le modèle mental nécessaire et de montrer son usage sur un cas concret.

La série s’adresse ainsi aux personnes qui souhaitent commencer le modding, découvrir les formats binaires, lire du Haskell dans un contexte concret ou mieux formaliser une pratique déjà empirique. Il n’est pas nécessaire de connaître Haskell ni d’avoir déjà étudié des formats binaires avant de commencer. Une première expérience du terminal et de la programmation facilitera les manipulations, mais les notions nécessaires seront présentées au moment où elles interviennent.

Ce ne sera pas pour autant un cursus exhaustif. Je développerai ce que j’ai moi-même besoin de comprendre ou de formaliser. Lorsqu’une notion périphérique est déjà bien traitée ailleurs, une explication courte et une référence pourront suffire.

↑ Retour au sommaire

Pourquoi écrire en français ?

J’ai longtemps hésité entre l’anglais, qui permettrait de toucher une communauté plus large, et le français, que je préfère lire et écrire. Je consulte quotidiennement de la documentation anglaise et j’ai vécu plusieurs années à Londres ; ce choix ne vient donc pas d’une difficulté à travailler en anglais.

J’ai finalement choisi le français parce que j’ai envie de contribuer à une documentation technique francophone précise et de travailler mon propre style dans cette langue. Je souhaite écrire les articles que j’aurais aimé trouver moi-même : naturels, rigoureux et suffisamment détaillés pour retrouver le raisonnement, pas seulement son résultat.

Le français de l’article n’impose pas de traduire tout le vocabulaire. Les identifiants du code resteront naturellement en anglais. Les noms officiels des champs et des structures seront conservés lorsqu’ils facilitent la consultation des sources. L’objectif n’est pas de franciser mécaniquement la terminologie, mais de choisir le mot qui décrit le plus exactement le concept.

↑ Retour au sommaire

Reproductibilité et corpus commercial

Les images et les ressources des jeux étudiés sont commerciales et ne seront pas distribuées avec les articles. Cette contrainte impose de distinguer le protocole documenté de l’expérience entièrement reproductible.

Une commande exécutée sur mon exemplaire produit une observation locale. Je peux publier la commande, l’offset, la règle d’interprétation et le résultat utile au raisonnement. Un lecteur qui travaille sur son propre exemplaire pourra reprendre ce protocole, mais il devra vérifier ses valeurs au lieu de supposer qu’elles sont identiques aux miennes.

La série comportera donc deux catégories principales d’articles.

Fondations reproductibles

Ces articles étudieront un mécanisme général sur des données synthétiques ou librement redistribuables : lecture binaire bornée, endianness, description de formats, validation, interprètes, diagnostics, tests ou gestion des effets.

Le code et les fixtures permettront d’exécuter les exemples, de modifier les valeurs et de provoquer les erreurs décrites. La CI exécutera les tests associés aux propriétés explicitement vérifiables sur ces données. Elle ne prétendra pas valider les observations réalisées sur les jeux commerciaux.

Applications au corpus

Ces articles appliqueront les mécanismes précédents aux exemplaires de jeux FromSoftware que j’étudie. Ils pourront publier des offsets, des métadonnées, des méthodes et des résultats bornés, mais pas les images de disque, les ressources commerciales, les clés ou les exécutables déchiffrés.

Un article pourra naturellement mêler les deux dimensions. Sa catégorie indiquera seulement de quoi dépend son résultat principal. Une difficulté rencontrée sur le corpus pourra fournir le problème d’un prochain article général ; une capacité validée sur des fixtures devra ensuite être vérifiée séparément sur les données réelles.

Cette alternance permet aussi de lire la série pour des raisons différentes. Une personne intéressée par Haskell mais ne possédant pas Demon’s Souls pourra exécuter les fondations reproductibles. Une personne surtout intéressée par le modding pourra suivre leur mise en application sans être obligée d’adopter toutes les abstractions étudiées.

↑ Retour au sommaire

← Revenir au sommaire de la série

Top comments (0)