Nous avons pris le temps de discuter avec Ben, l’un de nos programmeurs de l’interface du jeu, qui a corrigé le bug du texte crypté qui affectait Path of Exile depuis longtemps. Dans la mesure où ce bug était assez intéressant, il a eu la gentillesse de nous rédiger une autopsie, disponible ci-dessous :



Après avoir annoncé que nous avions enfin trouvé un correctif pour le tristement célèbre bug dit du texte crypté qui affectait certains joueurs depuis six ans, certains joueurs ont souhaité avoir des détails à propos de ce problème de longue date. J’ai toujours été intéressé par l’idée de m’essayer à rédiger une annonce portant sur des problèmes techniques, donc c’est l’occasion ou jamais de me lancer ! Ce bug était connu sous plusieurs noms auprès des joueurs, mais en interne le terme le plus courant était « texte crypté », donc c’est comme ça que je l’appellerai désormais.

Je peux vous dire que le bug est apparu dans le code le 25 avril 2016 et a été implémenté dans le jeu à l’occasion de la sortie de la version 2.3.0 et de la sortie de la ligue Prophecy. Il a été introduit à l’occasion d’une refactorisation du code gérant le texte du jeu, qui a été effectuée afin de pouvoir fonctionner avec la version Xbox de Path of Exile qui allait sortir.

Les symptômes

Je ne pense pas me tromper en disant qu’une majorité de joueurs a subi ce bug à un moment ou un autre au cours des années, le plus souvent après une longue session de jeu, mais certains joueurs étaient beaucoup plus souvent affectés que d’autres. Ce bug pouvait affecter n’importe quel texte du jeu, avec deux effets distincts. Le premier était le crénage, ou l’espace entre chacun des glyphes dessinés, qui était soit trop grand, soit trop petit. .



Le deuxième était que les caractères individuels étaient graphiquement représentés par le mauvais glyphe :


Certains joueurs ayant l’œil ont remarqué que dans le deuxième cas, l’application d’un chiffrement de substitution permettait de restaurer le texte original.

Exemple : Pevpf`^l D^j^db -> Physical Damage (Dégâts physiques), le symbole « ^ » correspondant ici à la lettre « a ».

Une chose qui nous a toujours semblé étrange était que les lettres majuscules avaient un symbole différent (voire pas de symbole du tout) par rapport aux lettres minuscules.

La chasse

Ce bug a été ajouté à notre liste des tâches le 4 juin 2016 suite à des signalements sur le forum postés juste après la sortie de la ligue Prophecy. Le souci principal, c’est que nous n’avons jamais réussi à reproduire ce bug de manière systématique sur nos machines. Nous ne l’avons reproduit que très rarement et de manière totalement aléatoire. De ce que j’en ai entendu, nous n’avons obtenu ce bug qu’une fois ou deux sur la machine de l’un des programmeurs, ce qui est crucial car cela nous permet d’inspecter la mémoire de l’ordinateur afin de récolter des indices et comprendre ce qui s’est mal passé. Sans méthode de reproduction fiable du bug, le mieux que nous pouvions faire était de faire des correctifs spéculatifs et d’espérer que ce problème cesserait d’être signalé. Et parce que rien n’avait été trouvé et qu’il ne s’agissait pas d’un problème critique, la priorité de ce bug a été diminuée afin de pouvoir consacrer plus de temps à de nouvelles fonctionnalités et autres correctifs.

De nombreux développeurs (moi y compris) ont tenté de corriger ce bug au cours des ans, mais chaque mois, de plus en plus de liens vers des signalements de bugs nous rappelaient l’existence de ce problème déroutant. À partir des retours des joueurs, captures d’écran et ma propre expérience, j’en ai déduit ceci :

  • Ce bug affectait les polices d’écriture individuelles (une combinaison de police d’écriture, taille, caractères en italique ou gras), plutôt que l’affichage du texte en lui-même.
  • Il ne s’agissait pas d’un bug de génération ou corruption des textures ou de l’antialiasing, dans la mesure où aucun des glyphes n’était coupé en deux ou partiellement coupé. Les rares fois où nous avons réussi à reproduire ce bug sur une machine de programmeur nous a permis de confirmer ce point.
  • Se déconnecter ne permettait pas de résoudre le problème la plupart du temps, il fallait redémarrer le client.
  • J’ai remarqué que nous n’avions jamais eu de signalement de ce bug sur Xbox, PlayStation ou MacOS, ce qui m’a permis de mieux cerner l’endroit du code posant souci.

Lors de la sortie de Scourge, j’ai remarqué que le bug commençait à être signalé un peu plus souvent et j’ai moi-même commencé à le subir plus souvent lors de mes propres sessions de jeu. J’ai enregistré la plupart de ces occurrences, récupéré des captures d’écran de joueurs et commencé à avoir des idées, mais je n’arrivais toujours pas à trouver de méthode pour reproduire ce bug excepté « jouer au jeu un bon moment ». Il y a environ deux semaines, j’ai eu un trou dans mon emploi du temps et j’ai décidé de faire une nouvelle tentative sérieuse de correction de ce bug. Je me suis donc plongé pendant plusieurs jours dans la partie du code du jeu relative aux textes, afin de tout lire et d’en comprendre toutes les subtilités.

Le correctif

Tout en naviguant à travers le code du texte du jeu, j’ai fini par tomber sur la fonction suivante :

SCRIPT_CACHE* ShapingEngineUniscribe::GetFontScriptCache( const Resources::Font& font )
{
    const auto font_resource = font.GetResource()->GetPointer();
    // `font_script_caches` here is a map of `const FontResource*` to `SCRIPT_CACHE` values
    auto it = font_script_caches.find( font_resource );
    if( it == std::end( font_script_caches ) )
        it = font_script_caches.emplace( std::make_pair( font_resource, nullptr ) ).first;
    return &it->second;
}

Pour les non programmeurs (parmi lesquels se trouve le traducteur de cette annonce), cette fonction prend une référence d’une ressource de police d’écriture spécifique et utilise son emplacement dans la mémoire en guise de clé (valeur de recherche) afin de créer une donnée SCRIPT_CACHE si jamais il n’y en avait pas déjà une. Cette fonction pointe ensuite vers la donnée SCRIPT_CACHE, ce qui permet au programme qui utilise cette fonction de modifier le SCRIPT_CACHE en mémoire plutôt qu’une copie dont les changements ne seraient pas persistants dans la carte « font_script_caches ».
La donnée SCRIPT_CACHE dont il est question ici est une donnée opaque utilisée par la bibliothèque Uniscribe de Windows (que nous n’utilisons que pour la version Windows du client). La documentation d’Uniscribe ne précise pas quelle information est véritablement stockée par cette fonction, uniquement que l’application doit conserver une de ces données pour chacun des « styles de caractères » utilisés. Toutefois, nous pouvons déduire à partir des effets du cryptage du texte que cette donnée est utilisée pour le crénage et l’affichage des textures des glyphes.

À première vue, cette fonction paraît faire quelque chose de parfaitement raisonnable, ce qui est probablement la raison pour laquelle le problème n’a jamais été décelé au cours de toutes ces années. Vous ne remarquez le problème que lorsque vous vous rendez compte que les ressources des caractères peuvent être déchargées par notre gestionnaire des ressources lorsqu’elles ne sont plus utilisées. Le bug survient lorsqu’un texte différent (différente police d’écriture, taille ou style) est chargé par le gestionnaire de ressources à cet emplacement exact de la mémoire, ce qui réutilise le précédent SCRIPT_CACHE.
Une fois que j’ai trouvé ça, j’ai fait deux ou trois tests afin de confirmer ma théorie qu’il s’agissait bien du problème.

Obliger tous les caractères à utiliser immédiatement le même script cache a généré ceci en démarrant le jeu :


Hourra ! Les deux aspects du bugs étaient là, ce qui était aussi une confirmation que ces effets étaient produits par le même bug plutôt que deux séparés. À partir de là, j’ai été en mesure de reproduire le bug naturellement en chargeant et déchargeant autant de caractères que possible jusqu’à ce qu’un nouveau caractère occupe l’emplacement d’un caractère précédent dans la mémoire :


Le problème ayant été identifié, il y avait plusieurs manières d’envisager le correctif. Nous pouvions déplacer la donnée SCRIPT_CACHE afin qu’elle appartienne à la partie Resource::Font du code, en effaçant l’ancienne donnée SCRIPT_CACHE à chaque fois qu’un caractère est déchargé, ou modifier la valeur de recherche dans la mémoire afin qu’elle soit associée à la police d’écriture, taille et style du caractère concerné, ce qui rend concrètement chaque caractère unique. Toutes ces options fonctionnent mais chacune a ses avantages et inconvénients, il faut donc choisir celle qui s’inscrit le mieux dans l’ensemble des systèmes du jeu.

Sommaire

L’origine de ce bug n’est pas si intéressante que ça en elle-même, il fallait juste se rendre compte que les adresses dans la mémoire peuvent être et sont réutilisées, il faut donc être prudent lorsqu’on réutilise ces adresses à répétition. Je me souviendrai cependant longtemps de ce bug en raison de ses symptômes si particuliers, de la difficulté que j’ai eu à l’identifier et de sa notoriété, étant donné qu’il a existé pendant longtemps. Il va même me manquer d’une certaine façon, car désormais mon cerveau a un « grand mystère » de moins pour s’occuper. J’imagine qu’il ne me reste plus qu’à trouver le prochain problème mystérieux et fascinant !

Merci à tous ceux qui ont signalé ce bug et tous les autres au cours des ans ! Le développement de programmes et la correction de bugs en particulier est quelque chose d’étrange, car des choses minuscules peuvent produire des bugs très bizarres. Des signalements de bugs détaillés sont toujours précieux afin de donner une idée claire du souci et de nous permettre de reproduire ce problème nous-même, ce qui nous permet ensuite de tester les correctifs plutôt que de tenter de corriger les problèmes à l’aveugle.


R.I.P T l e e v
Posté par 
le
Grinding Gear Games

Signaler

Compte à signaler :

Type de signalement

Infos supplémentaires