Billet #01
Ultima VI fait partie de ces jeux tellement classiques qu'ils ont été analysés sous toutes les coutures, et de pratiquement toutes les manieres possibles.
On peut trouver des dizaines de sites repertoriant une foule d'information sur:
- Le jeu lui-même; les personnages, l'histoire, la solution, les inconsistances, ...
- Ses aspects techniques; bugs, hacks, cheat, format des images, explications des fichiers, ...
- Il y a aussi des remakes; Nuvie, Project Ultima 6, ...
Mais jusqu'à présent, personne n'a, à ce qu'il me semble, cherché à décompiler le code du jeu.
C'est le défi que je me suis lancé il y a maintenant plusieurs mois, et autant vous dire que j'ai bien avancé.
Mettons-nous tout d'abord d'accord sur le terme "décompiler".
Et si vous ne savez pas ce qu'est un compilateur, je vous conseille d'arrêter ici la lecture, parce que ca ne va pas être joli à voir.
Mettons que vous avez un fichier source "toto.c" qui se compile en "toto.exe". Et bien décompiler "toto.exe", ça voudra dire créer un fichier "bobo.c" qui génère à la compilation un fichier identique à "toto.exe".
Bien entendu, vous perdez en route beaucoup d'informations de "toto.c"; les noms des variables, fonctions, les commentaires, les macros, et j'en passe.
Mais au moins, vous avez devant vous les algorithmes originaux, et partant de la, [...]
Prêts a soulever le capot ?
Ultima VI, dans sa version MSDOS, est constitué de plusieurs fichiers ".exe" et d'une multitude de ressources (images, musiques, dialogues, ...).
Pour ce qui est des ressources, internet regorge déjà d'informations tres intéressantes (ex: http://ultima.wikia.com/wiki/Ultima_VI_Internal_Formats).
Intéressons-nous d'abord au moteur du jeu, "GAME.EXE".
"GAME.EXE" a été compilé avec Turbo C version 2.0 (de la maintenant défunte compagnie Borland).
Et attention, ce n'est pas la version 2.1 (celle que l'on trouve le plus facilement sur le web).
Pourquoi c'est important ?
Parce que d'une part les compilateurs générent pour la même source du code différent,
et d'autre part, les fonctions de la librairie C diffèrent également.
Ce qui pose un problème quand on veut régénérer un exe à l'identique.
Toutes les fonctions C (à une ou deux exceptions près) sont compilées avec la convention d'appel PASCAL.
Et là vous vous dites: mais de quoi est-ce qu'il parle ?
Je sais, ça ne tient pas debout, dit comme ca, mais c'est juste pour la précision.
En fait, le choix de la convention d'appel n'a aucune incidence sur la programmation.
Borland, dans son manuel, indique que les fonctions compilées ainsi permettent de réaliser un gain d'espace dans l'exe et un gain de temps lors de l'appel.
Il y a juste une petite limitation qui empêche d'utiliser une specificité du langage C, le nombre variable d'arguments.
Enfin pour le coup si ça vous intéresse, je vous conseille d'aller faire une petite recherche sur le web, ça y sera beaucoup mieux expliqué.
Le modèle de mémoire utilisé est medium; c'est-à-dire que les fonctions sont appelées par défaut avec la convention FAR, et les données sont dans un seul segment.
Certaines routines (tres peu je vous rassure) semblent ne pas avoir été écrites en C, j'ai donc supposé qu'elles étaient à l'origine en assembleur.
Par rapport à l'époque, MASM version 3.0 m'a paru un bon choix.
Pour le désassemblage j'ai utilisé ce bon vieux "debug" sous l'irremplaçable DosBox.
Dans ma configuration, le segment 0 de l'exe (celui qui commence juste après l'en-tête) est chargé dans la memoire en 0838:0000.
C'est l'adresse qui m'a servi de base pour nommer les fonctions;
Les noms de fonctions (non encore renommées "humainement") utilisent le format:
"C_" + SEGMENT + "_" + OFFSET
et les noms de variables:
"D_" + OFFSET
Les données (par opposition au code) générées dans le modèle medium peuvent être réparties en deux grands groupes:
les données initialisées (ex: "int toto = 3;" hors du corps d'une fonction):
le compilateur les place, en fonction de l'ordre de leur apparition dans le source ".c", dans le segment _DATA
les données non-initialisées (ex: "int bobo;" hors du corps d'une fonction):
elles vont dans le segment _BSS;
L'ordre dépend cette fois du nom de la variable; alors ordre alphabetique, hash, ..., je n'ai pas encore cherché à percer le secret.
Pour ne pas me prendre la tête avec cette histoire d'ordre, j'ai décidé de sortir toutes ces variables des fichiers ".c" et de les réunir dans un seul écrit assembleur (histoire de pouvoir imposer l'ordre).
Ca fait partie des TODO.
enfin, à part quelques exceptions pour les longues listes de constantes, j'ai décidé de ne créer qu'un seul fichier en-tête regroupant toutes les macros et définitions externes: u6.h
Ca fait partie des TODO aussi.
Quelques sources d'informations:
http://www.ultimaaiera.com/blog/ulti...cal-documents/
Un ancien programmeur de Origin System a publié quelques documents de conceptions du jeu; ça m'a permis de retrouver le nom de certaines fonctions, ou données, ainsi que de comprendre certains aspects du code.
http://nuvie.sourceforge.net/phorum/...p?1,113,page=1
Ca c'est sur un forum, un autre programmeur a publié d'autres documents.
http://bitsavers.informatik.uni-stut...rland/turbo_c/
On peut y télécharger le manuel de programmation de la version 1.5.
Ce n'est pas la version qui nous intéresse, mais certains concepts importants (et valables dans la version 2.0) y sont assez bien expliqués.
Ultima VI fait partie de ces jeux tellement classiques qu'ils ont été analysés sous toutes les coutures, et de pratiquement toutes les manieres possibles.
On peut trouver des dizaines de sites repertoriant une foule d'information sur:
- Le jeu lui-même; les personnages, l'histoire, la solution, les inconsistances, ...
- Ses aspects techniques; bugs, hacks, cheat, format des images, explications des fichiers, ...
- Il y a aussi des remakes; Nuvie, Project Ultima 6, ...
Mais jusqu'à présent, personne n'a, à ce qu'il me semble, cherché à décompiler le code du jeu.
C'est le défi que je me suis lancé il y a maintenant plusieurs mois, et autant vous dire que j'ai bien avancé.
Mettons-nous tout d'abord d'accord sur le terme "décompiler".
Et si vous ne savez pas ce qu'est un compilateur, je vous conseille d'arrêter ici la lecture, parce que ca ne va pas être joli à voir.
Mettons que vous avez un fichier source "toto.c" qui se compile en "toto.exe". Et bien décompiler "toto.exe", ça voudra dire créer un fichier "bobo.c" qui génère à la compilation un fichier identique à "toto.exe".
Bien entendu, vous perdez en route beaucoup d'informations de "toto.c"; les noms des variables, fonctions, les commentaires, les macros, et j'en passe.
Mais au moins, vous avez devant vous les algorithmes originaux, et partant de la, [...]
Prêts a soulever le capot ?
Ultima VI, dans sa version MSDOS, est constitué de plusieurs fichiers ".exe" et d'une multitude de ressources (images, musiques, dialogues, ...).
Pour ce qui est des ressources, internet regorge déjà d'informations tres intéressantes (ex: http://ultima.wikia.com/wiki/Ultima_VI_Internal_Formats).
Intéressons-nous d'abord au moteur du jeu, "GAME.EXE".
"GAME.EXE" a été compilé avec Turbo C version 2.0 (de la maintenant défunte compagnie Borland).
Et attention, ce n'est pas la version 2.1 (celle que l'on trouve le plus facilement sur le web).
Pourquoi c'est important ?
Parce que d'une part les compilateurs générent pour la même source du code différent,
et d'autre part, les fonctions de la librairie C diffèrent également.
Ce qui pose un problème quand on veut régénérer un exe à l'identique.
Toutes les fonctions C (à une ou deux exceptions près) sont compilées avec la convention d'appel PASCAL.
Et là vous vous dites: mais de quoi est-ce qu'il parle ?
Je sais, ça ne tient pas debout, dit comme ca, mais c'est juste pour la précision.
En fait, le choix de la convention d'appel n'a aucune incidence sur la programmation.
Borland, dans son manuel, indique que les fonctions compilées ainsi permettent de réaliser un gain d'espace dans l'exe et un gain de temps lors de l'appel.
Il y a juste une petite limitation qui empêche d'utiliser une specificité du langage C, le nombre variable d'arguments.
Enfin pour le coup si ça vous intéresse, je vous conseille d'aller faire une petite recherche sur le web, ça y sera beaucoup mieux expliqué.
Le modèle de mémoire utilisé est medium; c'est-à-dire que les fonctions sont appelées par défaut avec la convention FAR, et les données sont dans un seul segment.
Certaines routines (tres peu je vous rassure) semblent ne pas avoir été écrites en C, j'ai donc supposé qu'elles étaient à l'origine en assembleur.
Par rapport à l'époque, MASM version 3.0 m'a paru un bon choix.
Pour le désassemblage j'ai utilisé ce bon vieux "debug" sous l'irremplaçable DosBox.
Dans ma configuration, le segment 0 de l'exe (celui qui commence juste après l'en-tête) est chargé dans la memoire en 0838:0000.
C'est l'adresse qui m'a servi de base pour nommer les fonctions;
Les noms de fonctions (non encore renommées "humainement") utilisent le format:
"C_" + SEGMENT + "_" + OFFSET
et les noms de variables:
"D_" + OFFSET
Les données (par opposition au code) générées dans le modèle medium peuvent être réparties en deux grands groupes:
les données initialisées (ex: "int toto = 3;" hors du corps d'une fonction):
le compilateur les place, en fonction de l'ordre de leur apparition dans le source ".c", dans le segment _DATA
les données non-initialisées (ex: "int bobo;" hors du corps d'une fonction):
elles vont dans le segment _BSS;
L'ordre dépend cette fois du nom de la variable; alors ordre alphabetique, hash, ..., je n'ai pas encore cherché à percer le secret.
Pour ne pas me prendre la tête avec cette histoire d'ordre, j'ai décidé de sortir toutes ces variables des fichiers ".c" et de les réunir dans un seul écrit assembleur (histoire de pouvoir imposer l'ordre).
Ca fait partie des TODO.
enfin, à part quelques exceptions pour les longues listes de constantes, j'ai décidé de ne créer qu'un seul fichier en-tête regroupant toutes les macros et définitions externes: u6.h
Ca fait partie des TODO aussi.
Quelques sources d'informations:
http://www.ultimaaiera.com/blog/ulti...cal-documents/
Un ancien programmeur de Origin System a publié quelques documents de conceptions du jeu; ça m'a permis de retrouver le nom de certaines fonctions, ou données, ainsi que de comprendre certains aspects du code.
http://nuvie.sourceforge.net/phorum/...p?1,113,page=1
Ca c'est sur un forum, un autre programmeur a publié d'autres documents.
http://bitsavers.informatik.uni-stut...rland/turbo_c/
On peut y télécharger le manuel de programmation de la version 1.5.
Ce n'est pas la version qui nous intéresse, mais certains concepts importants (et valables dans la version 2.0) y sont assez bien expliqués.
Commentaire