Annonce

Réduire

Charte du Forum

Sur ces forums, il est possible d'aborder n'importe quels sujets, de façon sérieuse ou tout simplement pour délirer !

Afin de préserver son harmonie et éviter les débordements, nous avons établi un ensemble de règles très simple que nous vous demandons de respecter.

1) Les thèmes suivants ne doivent jamais être abordés: La politique, La religion, La haine raciale, La pédophilie. Faites appel à votre bon sens pour éviter tout autre sujet susceptible de provoquer une polémique trop violente.

2) Ce forum est destiné a l'Abandonware (jeux a l'abandon). Les discussions relatives au Warez (requêtes, liens ...) seront systématiquement fermées ou effacées.

3) Lorsque vous lancez un sujet, veillez a ce qu'il soit dans le forum approprié (ne faites pas par exemple une requête dans le forum Discussion générale).

4) Avant de poser une question d'ordre technique, assurez vous d'avoir consulté la FAQ Abandonware-France et la FAQ Abandonware-Forums (questions fréquemment posées) !

5) Ne publiez pas d'images dans vos messages qui pourraient choquer les autres visiteurs.

6) Nous détestons le SPAM et le Flood ! Évitez donc de poster des messages inutiles et de façon répétée.

7) Le forum n'est pas un concours de posts. Il est inutile de poster des messages dans le simple but de monter en grade (Le nb de posts sera décrémenté en cas d'abus).

8) Les sujets privés destinés a une minorité sont interdits. Le forum est une communauté et vos messages sont destinés a l'ensemble des visiteurs. Nous mettons a votre disposition gratuitement des outils tels que Chat ou Messagerie privée.

9) Il est souhaitable de ne pas faire dévier un sujet. Cela n'est pas très sympa pour celui qui a lancé le sujet !

10) Peace and Love ! Les forums sont un endroit de détente, amusez vous, ne vous prenez pas la tête inutilement en public.

11) Les admins et modérateurs sont la pour vous protéger, et non pour vous faire la guerre ! Ils se réservent le droit de déplacer, modifier, fermer ou effacer un sujet en cas de besoin.

12) Concernant la zone arcade sur le forum, toute personne trichant se verra temporairement bannie du forum et TOUT ses scores seront purement et simplement effacés.

13) Ne proposez pas de lien vers du contenu illégal et n'encouragez pas au piratage d'oeuvres protégés par les droits d'auteurs.

14) Ce forum n'est pas un téléphone portable ! Corollaire à la proposition précédente: la plupart des gens susceptible de vous répondre n'a pas appris à lire le langage "texto/SMS". Vos messages ne sont pas limités à 160 caractères, alors s'il vous plait, écrivez correctement ! Clairement, on ne va pas vous tomber dessus pour un "s" oublié ou pour un accord incorrect, mais pas de "g chrché c je pandan dé mwa"! Copiez-collez votre message dans Word pour profiter du correcteur orthographique, au besoin.

Ces règles sont très importantes, merci de les respecter ! En cas de non respect, vous pourrez recevoir un avertissement, ou en fonction de la faute, être banni temporairement, voir définitivement du forum.
Voir plus
Voir moins

Ultima VI - les codes sources régénérés

Réduire
Ceci est une discussion importante.
X
X
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • #16
    Billet #06 - un mot sur la decompilation

    Ben oui, parce que j'ai l'impression que plutot que de me voir commenter le code source obtenu apres des heures, des jours, des mois de labeur, certains prefereraient me voir donner des explications sur le labeur en question...

    He bien c'est le sujet du post du jour ... mais je sens que vous allez etre decus.

    pour commencer, une liste des outils dont je me sers pour demarrer la decompilation (pour un jeu datant du debut des annees 90 maxi):
    - dosbox (l'indispensable)
    - debug.exe (une version qui tourne sous dosbox bien sur)
    - un editeur hexa

    Cette liste d'outils augmente au fur et a mesure que le projet avance, mais pour commencer, je veux juste recuperer des informations sur la genereation des executables du jeu.

    Au debut, je lance debug sur le programme qui m'interesse et je regarde un peu le debut du code.
    A ce niveau-la, je peut deja reconnaitre si le programme a ete compresse, avec exepack par exemple, et si c'est le cas, utiliser un programme (genre UNP) pour le decompresser et travailler sur le resultat obtenu: un exe non compresse !

    Un truc pratique, c'est d'avoir un peu en tete le format de l'entete d'une fichier ".exe".
    Avec les deux champs "nombre d'octets du dernier secteur" et "nombre de secteurs" on peut calculer la taille de l'exe principal.
    Si jamais le fichier est plus gros que cette taille, ca veut dire qu'il contient des overlays, des infos de debug, ou encore que sais-je.

    Dans le cas de GAME.EXE, pas de compression, pas d'overlays, pas d'infos de debug, ...

    Toujours dans debug, et en desassemblant le code du point d'entree, je lis:
    Code:
    -u
    
    0838:0000 BABD35        MOV	DX,35BD
    0838:0003 2E            CS:
    0838:0004 89160502      MOV	[0205],DX
    0838:0008 B430          MOV	AH,30
    0838:000A CD21          INT	21
    0838:000C 8B2E0200      MOV	BP,[0002]
    0838:0010 8B1E2C00      MOV	BX,[002C]
    0838:0014 8EDA          MOV	DS,DX
    0838:0016 A39200        MOV	[0092],AX
    0838:0019 8C069000      MOV	[0090],ES
    0838:001D 891E8C00      MOV	[008C],BX
    je vois ca, qu'est-ce que je fais ? Je tape:
    Code:
    d 35bd:0000
    et qu'est-ce que debug me repond ?
    Code:
    35BD:0000  00 00 00 00 54 75 72 62-6F 2D 43 20 2D 20 43 6F   ....Turbo-C - Co
    35BD:0010  70 79 72 69 67 68 74 20-28 63 29 20 31 39 38 38   pyright (c) 1988
    35BD:0020  20 42 6F 72 6C 61 6E 64-20 49 6E 74 6C 2E 00 4E    Borland Intl..N
    35BD:0030  75 6C 6C 20 70 6F 69 6E-74 65 72 20 61 73 73 69   ull pointer assi
    35BD:0040  67 6E 6D 65 6E 74 0D 0A-44 69 76 69 64 65 20 65   gnment..Divide e
    35BD:0050  72 72 6F 72 0D 0A 41 62-6E 6F 72 6D 61 6C 20 70   rror..Abnormal p
    35BD:0060  72 6F 67 72 61 6D 20 74-65 72 6D 69 6E 61 74 69   rogram terminati
    35BD:0070  6F 6E 0D 0A 00 00 00 00-00 00 00 00 00 00 00 00   on..............
    Je fais ca parce que j'ai deja pas mal de bouteille concernant les jeux des annees 80 sur PC.
    En gros ce qu'il faut savoir c'est qu'un programme ecrit dans un langage un peu evolue, genre C ou Pascal,
    ne commence pas directement par executer le code ecrit par le programmeur, mais par ce qu'on pourrait appeler un "chargeur" ou "loader" qui dans ses premieres instructions, fait reference a un moment ou un autre a un segment de donnees, lequel contient souvent une indication de copyright de la librairie utilisee.
    Bref, comme vous pouvez voir, le gagnant du jour est "Turbo c" dans sa version 1988.
    La prochaine etape sera de mettre la main dessus. Alors la, ca peut aller du totalement facile au vachement dur pour le retrouver.
    Par exemple, si vous chercher "turbo c" sur internet, grande sont les chances que vous tombiez sur la version "2.1".
    Les ayants-droits actuels des produits borland ayant eu la gentille idee de mettre en ligne GRATUITEMENT la version la plus recente du compilateur C.
    Manque de chance, la version qui nous interesse est anterieure. Comment le savoir ?
    En gros par, d'une part, le comportement du compilateur, et d'autre part, le contenu de la librairie.
    La charue se retrouvant legerement avant les boeufs, revenons un peu en arriere, a notre debug.

    Maintenant que je sais que c'est un compilateur C de borland qui a fait (presque tout) le boulot, je vais essayer de trouver le "modele de memoire" qui a ete utilise.
    (pour une description precise de ces modeles, je vous invite a aller faire une recherche sur internet, ca sera beaucoup mieux explique que si je m'y colle)
    Toujours avec debug, j'explore un peu plus le chargeur:
    Code:
    -u e9
    
    0838:00E9 BF2E32        MOV	DI,322E
    0838:00EC B9FAED        MOV	CX,EDFA
    0838:00EF 2BCF          SUB	CX,DI
    0838:00F1 F3            REPZ
    0838:00F2 AA            STOSB
    0838:00F3 0E            PUSH	CS
    0838:00F4 FF162832      CALL	[3228]
    0838:00F8 9A35063808    CALL	0838:0635
    0838:00FD 9A27073808    CALL	0838:0727
    0838:0102 B400          MOV	AH,00
    0838:0104 CD1A          INT	1A
    0838:0106 89169800      MOV	[0098],DX
    le contenu des registres DI et CX (322E et EDFA) nous donne respectivement le debut et la fin de la zone de donnes non initialisees.

    Code:
    -u
    
    0838:010A 890E9A00      MOV	[009A],CX
    0838:010E 0E            PUSH	CS
    0838:010F FF162C32      CALL	[322C]
    0838:0113 FF368800      PUSH	[0088]
    0838:0117 FF368600      PUSH	[0086]
    0838:011B FF368400      PUSH	[0084]
    0838:011F 9A5E0C0309    CALL	0903:0C5E
    0838:0124 50            PUSH	AX
    0838:0125 9A0C00E933    CALL	33E9:000C
    Et la nous avons l'appel de la fonction main, ou 0903:0C5E (et en bonus, la fonction exit, en 33E9:000C).

    Je remarque deja que les appels de fonctions se font avec une adresse 32 bits, les donnees etant par contre dans un seul segment, j'en deduisons que c'est le modele "medium".

    Et la phase de desassemblage peut commencer.
    Alors pour commencer, le plus facile, les donnes; il suffit de lancer debug en redirigeant la sortie standard dans un fichier, et de dumper le segment de donnees (35BD, on l'a trouve au debut) et 0 jusqu'a 322e, qui est le debut de la zone non initialisee.

    Pour le desassemblage, ca serait facile dans le cas d'un seul segment de code, mais vu que le modele est medium, ca veut dire qu'il faut reperer tous les segments, et les desassembler un par un.
    C'est plutot ennuyeux comme process, mais c'est comme ca que je fais :-(

    Un petit coup d'oeil au passage au resultat de la commande 'r' sous debug:
    Code:
    -r
    
    AX=FFFF  BX=0003  CX=3CA0  DX=0000  SP=0080  BP=0000  SI=0000  DI=0000  
    DS=0828  ES=0828  SS=449D  CS=0838  IP=0000   NV UP EI PL NZ NA PO NC 
    0838:0000 BABD35        MOV	DX,35BD
    Le registre DS (ou ES, ils contiennent la meme valeur au debut) indique a quel segment commence le PSP du process (= executable en cours d'execution).
    Le PSP fait 256 octets et est immediatemment suivi de l'executable charge en memoire (moins son en-tete et apres relocation).
    Pour trouver le segment en question, on fait "segment de psp" + (256 / 4); en hexadecimal, le calcul donne 0x0828 + 0x100 / 4 = 0x0828 + 0x10 = 0x0838
    (Il se trouve que c'est le segment ou se trouve le loader, mais ce n'est pas toujours le cas)
    Sachant que le segment de donnees commence en 35BD et que l'exe a ete charge en 0838, il reste donc (0x35BD - 0x0838) * 0x10 octets a analyser;
    le resultat: 0x2d850, ce qui depasse la taille d'un segment.

    Donc pour commencer, je desassemble le segment 0838 jusqu'au segment 0903 (celui qui contient le main, rappelez-vous), cela fait une taille maximum de 0xcbf octets;
    je suppose que le tout correspond a un seul segment.
    (spoiler: c'est bien le cas! Mais a ce stade-la, pas moyen d'en etre sur).

    Alors un autre indice; ce segment 0838 contient le chargeur, et dans le cas de Turbo C, ce segment s'appelle a l'origine _TEXT;
    Il contient les fonctions assembleur de la librairie C, mais en farfouillant le code, je trouve aussi (par exemple a partir de l'offset 0207) des fonctions qui ne semblent pas faire partie de la librairie C;
    ce sont des fonctions ecrites vraisemblablement en assembleur, dont le segment de code a ete nomme _TEXT et qui se retrouvent donc dans ce fameux segment 0838.

    [ce sont les fonctions OSI_file dont j'ai parle dans un post precedent]

    Le segment 0838 s'arrete a l'octet 0838:0cb0 (compris), le segment 0903 commence donc en 0903:0001.
    Et c'est la que ca devient rebarbatif, parce que je dois trouver tous les segments entre 0903 et 35BD maintenant.
    Perso, j'y vais a l'arrache; je desassemble, je verifie, j'efface ce qu'il y a en trop, je fais des recherches textuelles pour voir si il est fait reference a tel ou tel segment, ...
    Au final, j'obtiens un fichier texte assez consequent (dans les 3 megaoctets) qui contient l'integralite de l'exe original sous forme desassemblee pour le code, dumpee pour les donnees.

    Dans la mesure du possible, je repere toutes les fonctions de la libc, retrouve leur nom original, et remplace leurs appels, par exemple:
    Code:
    -u1944:17b9
    
    1944:17B9 33C0          XOR	AX,AX
    1944:17BB 50            PUSH	AX
    1944:17BC 8D5EFC        LEA	BX,[BP-04]
    1944:17BF 8CD2          MOV	DX,SS
    1944:17C1 8BC3          MOV	AX,BX
    1944:17C3 B90400        MOV	CX,0004
    1944:17C6 9A8A0C3808    CALL	0838:0C8A
    1944:17CB 8B1EB3B6      MOV	BX,[B6B3]
    1944:17CF D1E3          SHL	BX,1
    1944:17D1 8B874835      MOV	AX,[BX+3548]
    1944:17D5 25FF03        AND	AX,03FF
    1944:17D8 50            PUSH	AX
    par le plus lisible:

    Code:
    1944:17B9 33C0          XOR	AX,AX
    1944:17BB 50            PUSH	AX
    1944:17BC 8D5EFC        LEA	BX,[BP-04]
    1944:17BF 8CD2          MOV	DX,SS
    1944:17C1 8BC3          MOV	AX,BX
    1944:17C3 B90400        MOV	CX,0004
    1944:17C6 9A8A0C3808    CALL	SPUSH@
    1944:17CB 8B1EB3B6      MOV	BX,[B6B3]
    1944:17CF D1E3          SHL	BX,1
    1944:17D1 8B874835      MOV	AX,[BX+3548]
    1944:17D5 25FF03        AND	AX,03FF
    1944:17D8 50            PUSH	AX
    Pour la petite histoire, SPUSH@ est une fonction utilitaire qui permet "d'empiler" un objet structure (struct) pour en faire un parametre dans un appel de fonction.
    Ce qui est cool c'est que ca vous donne deux informations importantes sur l'objet en question:
    - le fait que c'est une structure
    - sa taille (dans notre cas, 4 octets)

    Il ne me reste plus qu'a traduire 3 mega-octets de code assembleur en langage C ....
    Mais ca sera pour la prochaine fois ...

    Commentaire


    • #17
      Envoyé par ergonomy_joe Voir le message
      Il ne me reste plus qu'a traduire 3 mega-octets de code assembleur en langage C ....
      Mais ca sera pour la prochaine fois ...
      Paradoxalement, je suis parfois plus à l'aise avec l'assembleur que le C Ceci dit, c'est toujours intéressant de savoir comment tu t'y prends ... D'ailleurs, comment fais-tu pour "sauvegarder" ce que sort DEBUG quand tu désassembles telle ou telle partie du code ? Tant qu'il n'y a qu'un segment, c'est "simple", mais c'est malheureusement souvent pas le cas et souvent tu dois "trafiquer" pour récupérer l'intégralité du code

      Steph

      Commentaire


      • #18
        C'est du travail d'orfèvre, je ne pensais pas que c'était si compliqué.
        C'est finalement logique quand on lit tes explications, et je ne verrai plus les sources du LMDA du même œil

        Commentaire


        • #19
          @Stephh
          Je redirige la sortie standard de debug avec ">". Et apres je tape mes commandes de desassemblage/dumpage "en aveugle".
          Pour le probleme que tu souleves sur le desassemblage en cas de multiples segment, je ne peux qu'approuver. En meme temps, debug n'est pas tout jeune, et il existe sans doute des outils plus pratiques (IDA par exemple ?); mais va savoir pourquoi, je prefere travailler avec ce bon vieux debug

          @Karamoon
          LDMA c'etait du Turbo Pascal, donc un seul segment, et une "librairie standard" toujours placee au meme endroit. A part ca c'est vrai que la demarche ressemble beaucoup.
          Pour les fanas, voila un lien vers une page sur le fonctionnement interne du compilateur (et par la meme sur le code genere): http://www.pcengines.ch/tp3.htm


          Billet #07 - un mot sur la decompilation ... suite ... et fin ?

          Wouhou, cette discussion vient de depasser les 1000 acces ... Ca fait une moyenne d'une cinquantaine de vision par billet... voila, c'etait juste pour le dire. Reprennons maintenant le cours de la discussion precedente.

          En faisant des tests avec le compilateur C, on peut extraire quelques informations importantes sur le modele medium, et le code genere.
          Par exemple, "un segment" = "un fichier .c"
          Ca me permet de mettre en forme mon projet en commencant par creer tous les fichiers ".c" dont j'ai besoin, en commencant par "seg_0903.c".
          Bien sur, tous vides au debut.
          Ensuite, pour chacun de ces segments (on peut dire aussi "module"), je delimite dans le code, les fonctions.
          Le debut, on le reconnait facilement, ca ressemble souvent a:
          Code:
          0903:0001 55            PUSH	BP
          0903:0002 8BEC          MOV	BP,SP
          0903:0004 83EC08        SUB	SP,+08
          en prime, le "sub sp,8" m'indique la taille des variables locales.

          La fin de la fonction ? Je desassemble un peu plus bas, et

          Code:
          0903:00D8 8BE5          MOV	SP,BP
          0903:00DA 5D            POP	BP
          0903:00DB CA0600        RETF	0006
          
          0903:00DE 55            PUSH	BP
          0903:00DF 8BEC          MOV	BP,SP
          0903:00E1 83EC0C        SUB	SP,+0C
          0903:00E4 56            PUSH	SI
          0903:00E5 57            PUSH	DI
          le "retf 6" lui, m'indique que la fonctions en a pour 6 octets de parametres. Ca me donne au passage une autre imformation capitale: cette fonction utilise la convention d'appel Pascal (si elle etait CDECL, on aurait juste RETF. Pour des eclaircicements, vous pourrez relire les anciens posts, farfouiller sur le web).

          et vous voyez qu'une nouvelle fonction demarre en 0903:00DE, a pour 12 octets ("c" en hexadecimal) de variables locales, et utilise les registres SI et DI.

          J'ai deja parle de ma convention de nommage dans un post precedent, donc vous avez maintenant compris que les deux fonctions que je viens de trouver vont s'appeller respectivement C_0903_0001 et C_0903_00D8, et partout ou je trouve un appel a ces fonctions, je remplace l'esoterique "CALL 0903:0001" par le beaucoup plus parlant "CALL C_0903_0001";
          je plaisante .. un peu .. parce que le renommage a tout de meme pas mal d'avantage, pour une recherche textuelle par exemple, pour que mon code C et le listing desassemble utilise les memes noms aussi.

          Une particularite du compilateur de Borland, c'est de remplacer un appel de fonction FAR (sur 32 bits) par un appel NEAR (sur 16 bits) precedes d'un "push CS" dans le cas d'un appel d'une fonction declaree dans le meme module que l'appelant, et en amont.

          un exemple tout de suite:
          Code:
          0903:07CC B82C8C        MOV	AX,8C2C
          0903:07CF 50            PUSH	AX
          0903:07D0 33D2          XOR	DX,DX
          0903:07D2 B80003        MOV	AX,0300
          0903:07D5 52            PUSH	DX
          0903:07D6 50            PUSH	AX
          0903:07D7 0E            PUSH	CS
          0903:07D8 E826F8        CALL	0001
          Il faudra juste faire attention a le renommer "call C_0903_0001" aussi.

          Les fonctions C sont generees dans l'ordre de leur apparation dans le fichier source, et le compilateur borland etant assez peu optimise, il est assez facile de traduire au fur et a mesure le code tel qu'on le lit, de haut en bas.

          Par exemple,
          Code:
          0903:00E6 8B4606        MOV	AX,[BP+06]
          0903:00E9 0B4608        OR	AX,[BP+08]
          0903:00EC 7403          JZ	00F1
          0903:00EE E97901        JMP	026A
          me permet d'imaginer une declaration de la forme

          Code:
          C_0901_00DE( ... long /*ou far pointer, ou unsigne long ...*/ bp06 ...) {
          ...
          if(bp06 == 0)
          ...
          toujours dans mes conventions de nommages, locales cette fois, en cas de parametre, [bp + nn], je nomme la variable "bpnn", et en cas de variable locale, [bp - nn] je la nomme "bp_nn".
          pour ce qui est du type de bp06, je ne peux pas encore etre sur du type. En deroulant la traduction je pourrais accumuler des indices et positionner un type plus valable.

          autre exemple de traduction
          Code:
          0903:00F1 A13032        MOV	AX,[3230]
          0903:00F4 33D2          XOR	DX,DX
          0903:00F6 B104          MOV	CL,04
          0903:00F8 9A0B0C3808    CALL	LXLSH@
          0903:00FD 8B1E2E32      MOV	BX,[322E]
          0903:0101 33C9          XOR	CX,CX
          0903:0103 03C3          ADD	AX,BX
          0903:0105 13D1          ADC	DX,CX
          0903:0107 8956FA        MOV	[BP-06],DX
          0903:010A 8946F8        MOV	[BP-08],AX
          
          0903:010D 250F00        AND	AX,000F
          0903:0110 81E20000      AND	DX,0000
          0903:0114 0BD0          OR	DX,AX
          0903:0116 7448          JZ	0160
          devient
          Code:
          BP_08 = ((unsigned long)D_3230 << 4) + D_322E;
          La, vous vous voyez que j'ai prefixe un "D_" devant l'adresse des variables globales.
          Comme vous avez une bonne memoire, vous vous rappelez que les variables non initialisees commencent a 322E, ce qui est le cas de notre variable.
          (par contre niveau nommage, je ne fais pas de distinction entre initialisee et non initialisee)

          Et voila, je deroule le code comme ca, gentiment mais surement, fonction apres fonction, module apres module.
          Je transcris, je compile, je corrige les erreurs de compilation, je compare avec le code original, en cas de difference, j'affine le code.

          Bien ... ce billet me parait bien cours. En meme temps, a part donner des exemples, ou rentrer dans le detail d'un point particulier (les chaines de caracteres, les pointeurs, ...), je ne vois pas vraiment quoi dire de plus.
          Mettons que je pourrais toujours approfondir le sujet dans un prochain post.

          Commentaire


          • #20
            Post #08 - et si nous parlions d'INSTALL.EXE

            Ca fait un long moment que je n'ai rien poste dans ce sujet, et je m'en excuse.

            Pour me faire pardonner, je vous propose, en guise de recreation, le source complet d'un autre executable: INSTALL.EXE

            U6_SRC_INST_20131206.zip

            Ce programme est constitue de quatre modules:
            -seg_0909: le module principal (en language C et qui contient le main)
            -seg_0a57: un module qui teste les capacites la carte graphique (en assembleur)
            -seg_0a68: un module de decompression (en assembleur egalement)
            -OSI_file : un module de gestion des fichiers (en assembleur)

            Je n'ai pas inclu ce dernier module dans l'archive parce qu'il a deja ete poste.

            Par contre j'ai mis le fichier batch (doit.bat) dont je me sers pour regenerer le programme; il n'est pas tres lisible, mais c'est surtout pour attirer votre attention sur la presence du module "gotoxy" dans la ligne du linker.
            Ce module est fourni avec la librairie Turbo C (CM.LIB dans notre cas, c'est d'ailleurs de la que je l'ai extrait).
            Et il contient la fonction ... gotoxy ! Cette fonction permet de positionner le curseur sur l'ecran en precisant son abscisse et son ordonee: x et y si vous voulez.

            Mais alors pourquoi a-t-on de preciser son inclusion alors qu'il est deja present dans la librairie ?

            Bonne question!

            Tout ca, c'est a cause de sa localisation dans l'executable. En principe, les modules sont linkes dans l'odre dans lequel ils aparaissent lors de l'invocation de linker.
            Et ensuite, on trouve ceux qui sont issus d'une librairie.
            Et allez savoir pourquoi, gotoxy se retrouve entre deux modules de Origin (OSI_file, et seg_0909), donc si j'avais omis gotoxy dans la liste des modules, l'ordre d'inclusion aurait ete different de celui de l'exe original.

            Voila, c'etait ma solution a ce petit probleme technique; neamoins, je n'arrive toujour pas a comprendre la presence de ce "gotoxy" au milieux des autres modules ... :-/

            Commentaire


            • #21
              Assembleur et optimisations

              Quand je regarde le code je suis surpris par la recherche de performances.

              Par exemple par l'utilisation de l'assembleur, détecter le hardware en assembleur c'est logique, mais la décompression et la gestion des fichiers pourrait être réalisée en C. C'est un langage assez rapide et en plus l'installation n'est faite qu'une seule fois.

              Et un peu plus loin dans le code il y a une conversion des fichiers "tiles" en fonction de la carte vidéo, là aussi c'est surprenant je me serais attendu à ce que la conversion soit réalisée en mémoire à chaque lancement du jeu.

              Je suppose que ces optimisations ont un sens à l'époque des i286.

              Commentaire


              • #22
                @karamoon
                Tu as raison, d'autres programme de la meme epoque (par exempl Moebius je crois) utilisent du code en C pour la decompression.
                Mais il semble que pour Ultima 6 le code soit issu d'un freeware (cf. la reference a Tom Pfau), donc il est possible que Origin ait acquis une librairie pour la compression/decompression et l'ait utilisee telle quelle.
                On retrouve d'ailleurs le meme code dans Ultima 5, puis dans d'autres jeux Origin de cette periode.

                Au fait, Bonne Annee a toutes et a tous ! Precipitons-nous de ce pas vers le ...

                Post #09 - Seance

                ... et expliquons ce titre curieux. Il s'agit tout simplement du nom d'un des nombreux sorts que vous pouvez lancer dans le jeu.
                Celui-la permet d'avoir une conversation avec un mort. Vous ne me croyez pas ? Regardons-le code alors:
                Code:
                /*"seance"*/
                static C_1944_3C30(int aFlag) {
                	int si;
                
                	CON_printf(/*D_149D*/"%s", D_0DDC[9]);
                	if(!aFlag) {
                		MouseMode = 1;
                		CON_getch();
                	}
                	CON_printf(/*D_14A6*/"\n");
                	MagicalHalo(Party[Active], 0);
                	si = GetType(Selection.obj);
                	if(Selection.obj == -1 || (si != OBJ_153 && si != OBJ_162 && si != OBJ_155 && si != OBJ_154)) {
                		CON_printf(/*D_14BA*/"\n%s\n", D_0DDC[2]);
                		MUS_0065(13, 0);
                		return;
                	}
                	CON_printf((char *)GetObjectString(Selection.obj));
                	CON_printf(/*D_14A6*/"\n");
                	SeanceFlag = 1;
                	TALK_initTalk(Active, GetQual(Selection.obj));
                	SeanceFlag = 0;
                }
                (Entre parentheses, vous avez echappe au titre de post "it's a kind of magic")

                Il n'y a pas grand chose a se mettre sous la dent. La fonction verifie que vous avez selectionne un cadavre comme interlocuteur:
                OBJ_153 pour un humain
                OBJ_162 pour une souris (enfin je crois)
                OBJ_155 pour une gargouille
                OBJ_154 pour un cyclope

                puis positionne le flag SeanceFlag et appelle la fonction TALK_initTalk:
                Code:
                /*C_16E1_0007*/TALK_initTalk(int partyId, int objNum) {
                	int si;
                
                	/*set strings $*/
                	VarStr['G' - 0x37] = D_2CCA?/*D_0920*/"milady":/*D_0927*/"milord";/*[g]reetings*/
                	VarStr['N' - 0x37] = /*D_092E*/"lots of space for any possible NPC name";/*[n]ame of NPC*/
                	VarStr['P' - 0x37] = Names[partyId];/*name of PC*/
                	VarStr['T' - 0x37] =
                		(Time_H < 12)?/*D_0956*/"morning":
                		(Time_H < 18)?/*D_095E*/"afternoon":
                		/*D_0968*/"evening"
                	;/*[t]ime of day*/
                	/*set variables #*/
                	VarInt['A' - 0x37] = GetDex(Party[partyId]);/*PC's [a]gility*/
                	VarInt['D' - 0x37] = Date_D;/*[d]ay*/
                	VarInt['E' - 0x37] = ExpPoints[Party[partyId]];/*PC's [e]xperience*/
                	VarInt['G' - 0x37] = (D_2CCA != 0);/*[g]ender?*/
                	VarInt['H' - 0x37] = Time_H;/*[h]our*/
                	VarInt['I' - 0x37] = GetInt(Party[partyId]);/*PC's [i]ntelligence*/
                	VarInt['K' - 0x37] = KARMA;/*[k]arma*/
                	VarInt['L' - 0x37] = D_2CA8;/*[l]anguage*/
                	VarInt['M' - 0x37] = Date_M;/*[m]onth*/
                	VarInt['O' - 0x37] = PartySize - 1;
                	VarInt['P' - 0x37] = HitPoints[Party[partyId]];/*PC's hit [p]oints*/
                	VarInt['S' - 0x37] = GetStr(Party[partyId]);/*PC's [s]trength*/
                	VarInt['W' - 0x37] = NPCMode[objNum];/*NPC's [w]ork type*/
                	VarInt['Y' - 0x37] = Date_Y;/*[y]ear*/
                	/*-- reset $0~$9 --*/
                	for(si = 0; si < 10; si ++)
                		VarStr[si] = D_E7AD[si];
                	/*-- reset #0~#9 --*/
                	for(si = 0; si < 10; si ++)
                		VarInt[si] = 0;
                	/*--*/
                	D_04DF->_attr |= 0x10;
                	MUS_091A(0);
                	TalkDriver(Party[partyId], objNum);
                	MUS_09A8();
                	D_049F = 1;
                	D_2CA8 = VarInt['L' - 0x37];
                }
                Le moteur de conversation lui-meme se trouve dans la fonction TalkDriver.
                Code:
                /*C_1703_1E23*/TalkDriver(int objNum_0, int objNum_1) {
                	int si, di;
                	int opcode, dir, bp_06, bp_04, objTyp;
                
                	D_E796[1] = objNum_0;
                	D_E796[0] = objNum_1;
                	if(IsPlrControl(D_E796[0]) && !C_1703_0153(D_E796[0])) {
                		CON_printf(/*D_0A13*/"\nNot on screen.\n");
                		return;
                	}
                	if(D_2CC3 != -1 && D_2CC3 != 0) {
                		CON_printf(/*D_0A24*/"\nNot in solo mode.\n");
                		return;
                	}
                	objTyp = GetType(objNum_1);
                	/*status or shrine*/
                	if(objTyp == OBJ_18D || objTyp == OBJ_18E || objTyp == OBJ_18F || objTyp == OBJ_189)
                		D_E796[0] = GetQual(objNum_1);
                
                	if(D_E796[0] == 0 || D_E796[0] >= 0xe0) {
                		if(IsDead(D_E796[0]) && SeanceFlag) {
                			CON_printf(/*D_0A38*/"\nYou hear a deep moan.\n");
                			return;
                		}
                	}
                
                	if(D_E796[0] == objNum_1) {
                		if(
                			(IsDead(D_E796[0]) && !SeanceFlag) ||
                			IsAsleep(D_E796[0]) || IsParalyzed(D_E796[0]) ||
                			NPCMode[D_E796[0]] == AI_VIGILANTE ||
                			NPCMode[D_E796[0]] == AI_FEAR ||
                			NPCMode[D_E796[0]] == AI_RETREAT ||
                			NPCMode[D_E796[0]] == AI_ARREST ||
                			GetAlignment(D_E796[0]) == EVIL ||
                			GetAlignment(D_E796[0]) == CHAOTIC
                		) {
                			CON_printf(/*D_0A50*/"\n\nNo response\n");
                			return;
                		}
                	}
                	if(IsArmageddon) {
                		CON_printf(/*D_0A50+1*/"\nNo response\n");
                		return;
                	}
                	if(D_E796[1] == D_E796[0]) {
                		CON_printf(/*D_0A5F*/"\nTalking to yourself?\n");
                		return;
                	}
                
                	dir = MkDirection(PointerX, PointerY);
                	if(dir != -1) {
                		C_1E0F_0664(Party[Active], dir);
                		C_1100_0306();
                	}
                
                	if(
                		D_E796[0] == 0 ||
                		(D_E796[0] >= 0xe0 && objTyp != OBJ_17E && objTyp != OBJ_175 && objTyp != OBJ_16B && objTyp != OBJ_16A) ||
                		!LoadConversation(D_E796[0], TalkBuf)
                	) {
                		CON_printf(/*D_0A76*/"\nFunny, no response.\n");
                		return;
                	}
                	C_1703_028B();/*compute #N*/
                	Talk_PC = 0;
                	/*skip OP__FF ncpnum*/
                	si = 2;
                	/*get the npc name: $N*/
                	while((opcode = TalkBuf[si++]) != OP_DESC)
                		VarStr['N' - 0x37][si - 3] = opcode;
                	VarStr['N' - 0x37][si - 3] = 0;
                	/* */
                	IsInConversation = 1;
                	bp_06 = StatusDisplay;
                	bp_04 = D_04B3;
                	if(!SeanceFlag) {
                		if(GetType(objNum_1) != OBJ_189)
                			C_27A1_02D9(objNum_1);/*display character's portrait*/
                		CON_printf(/*D_0A8C*/"\n\nYou see ");
                		while(PARSE_U8 != OP_DESC);
                		D_E79D = Talk_PC;
                		parse_statement(D_E796[0]);
                	}
                	CON_printf(/*D_098E + 0Bh*/"\n");
                	while(PARSE_U8 != OP_MAIN);
                
                	opcode = PARSE_U8;
                	if(opcode != OP_ASKTOP) {
                		Talk_PC --;
                		parse_statement(D_E796[0]);
                	} else {
                		Talk_PC --;
                	}
                
                	while(!mustLeave) {
                		D_E7A7 = Talk_PC;
                		opcode = PARSE_U8;
                		if(opcode == OP_ASKTOP) {
                			CON_printf(/*D_0A97*/"\nyou say:");
                			CON_gets(D_E732, 50);
                			CON_printf(/*D_098E + 0Bh*/"\n");
                			D_E79C = 0;
                		} else if(opcode == OP_GETSTR) {
                			CON_gets(D_E732, 50);
                			si = PARSE_U8;
                			if(PARSE_U8 != OP_VARSTR)
                				break;
                			strcpy(VarStr[si], D_E732);
                			CON_printf(/*D_0AA1*/"\n\n");
                			D_E79C = 0;
                		} else if(opcode == OP_GETINT) {
                			CON_gets(D_E732, 50);
                			si = PARSE_U8;
                			if(PARSE_U8 != OP_VARINT)
                				break;
                			di = C_27A1_0205(D_E732, 0);
                			VarInt[si] = (di == -1 || di == -2)?0:di;
                			CON_printf(/*D_0AA1*/"\n\n");
                			D_E79C = 0;
                		} else if(opcode == OP_GETDIGIT) {
                			do {
                				D_E732[0] = CON_getch();
                				D_E732[1] = 0;
                				di = C_27A1_0205(D_E732, 0);
                				if(D_E732[0] == '\r' || D_E732[0] == 27)
                					di = 0;
                			} while(di == -1 || di == -2);
                			si = PARSE_U8;
                			if(PARSE_U8 != OP_VARINT)
                				break;
                			VarInt[si] = di;
                			CON_printf(/*D_0AA4*/"%d\n\n", di);
                			D_E79C = 0;
                		} else if(opcode == OP_GETCHR) {
                			D_E732[0] = CON_getch();
                			D_E732[1] = 0;
                			CON_printf(/*D_0AA9*/"%s\n\n", D_E732);
                			D_E79C = 0;
                		} else if(opcode == OP_WAIT) {
                			CON_getch();
                		} else if(opcode == OP_GET) {/*OP_GET c0 .. cn OP_KEY -- GET [...]*/
                			for(si = 0; (opcode = PARSE_U8) != OP_KEY; si ++)
                				D_E728[si] = opcode;
                			Talk_PC --;
                			D_E728[si] = 0;
                			do {
                				opcode = CON_getch();
                				for(si = 0; D_E728[si] && toupper(D_E728[si]) != opcode; si++);
                			} while(D_E728[si] == 0);
                			if(opcode >= ' ')
                				CON_printf(/*D_0AAE*/"%c", opcode);
                			D_E732[0] = opcode;
                			D_E732[1] = 0;
                			CON_printf(/*D_098E + 0Bh*/"\n");
                			D_E79C = 0;
                		} else if(opcode == OP_KEY) {
                			CON_printf(/*D_0AB1*/"\nCommand for input key expected.");
                			break;
                		} else if(opcode == OP__FF) {
                			break;
                		} else if(opcode == OP_ENDRES) {
                			/**/
                		} else {
                			CON_printf(/*D_0AD2*/"\nLogical error occured.");
                			break;
                		}
                		/*$Z is the last player's input*/
                		VarStr['Z' - 0x37] = D_E732;
                
                		if(stricmp(D_E732, /*D_0AEA*/"look") == 0) {
                			CON_printf(/*D_0A8C + 2*/"You see ");
                			Talk_PC = D_E79D;
                			parse_statement(D_E796[0]);
                			Talk_PC = D_E7A7;
                		} else {
                			parse_statement(D_E796[0]);
                		}
                	}
                	mustLeave = 0;
                	IsInConversation = 0;
                	if(PartySize == D_04B3) {
                		D_04B3 = 0;
                		D_07CC = 0;
                		StatusDisplay = CMD_91;
                	} else {
                		StatusDisplay = bp_06;
                		D_04B3 = bp_04;
                	}
                	StatusDirty ++;
                }
                Voila, ca fait beaucoup de code d'un coup, mais je voulais vous montrer toutes les apparitions de SeanceFlag.

                En plus, ca vous permet de voir la boucle du moteur de conversation (entre parenthese, vous avez echappe au titre "you talkin' to me" deja pris: http://www.floppy-legend.fr/v2/news/news.php?id=13).
                Le moteur de conversation a base de scripts fait partie des grosses evolutions importantes de ce titre par rapport a ces predecesseurs.

                Mais revenons a seance.

                Dans le jeu original, en se rendant dans la ville de Skara Brae nous apprenons qu'un denomme Quenton a ete assassine, et on trouve son fantome errant aux alentours de la ville.
                Certains joueurs acharnes semblent avoir ete decus que la resolution du meurtre ne fasse pas partie du jeu et en ont deduit a un bug.
                En voila un qui a une theorie interessante (mais en anglais): http://deathamster.flyingomelette.co...s/quenton.html
                Et ici une discussion sur une correction possible (mais toujours en anglais): http://nuvie.sourceforge.net/phorum/read.php?1,1049

                Dans Ultima 7 l'identite du meurtrier est revelee, de plus on peut parler a Quenton en utilisant le sort Seance.
                Mais dans Ultima 6 on peut entamer une conversation avec lui sans l'aide de ce sort. En revanche, aucun son ne sort de sa bouche et il se contente de faire des gestes.

                La conclusion de ce post est donc que je ne sais pas si les scenaristes du jeu ont oublie d'inclure des dialogues pour resoudre l'enigme, ou bien si tout simplement ils ont fait expres de laisser le joueur dans le flou, mais en etudiant le code, il me parait clair que le sort Seance n'a rien a voir avec Quenton.
                D'abord, Quenton n'est pas un cadavre, mais un fantome (cf le test de la fonction C_1944_3C30);
                Ensuite, FlagSeance n'est teste que 2 fois:
                .la premiere pour s'assurer que l'interlocuteur est bien mort quand on utilise ce sort
                .la deuxieme, pour "sauter" l'affichage du portrait de l'interlocuteur
                Donc en cours de conversation, rien n'a ete prevu pour que ce flag influe sur le deroulement du script
                Et enfin, [SUBJECTIF]en supposant que Quenton soit capable de dire le nom de son assassin, que va faire le joueur ?
                L'executer de ses propres mains ? Vous perdrez du karma et si il y a des gardes alentours, c'est vous qui finirez en prison
                Le traduire en justice ? cette fonction malheureusement de s'applique qu'aux mauvaises actions du joueur
                [/SUBJECTIF]

                Voila, c'est tout pour aujourd'hui.

                Commentaire


                • #23
                  Passionnant, tout simplement passionnant. Je programme aussi, et je vois la difficulté de cette tache.

                  Tiens, je partage un autre projet fait par un français qui a pour but de re-créer les executables pour jouer à Jill of the jungle. Donc en ne prenant que les fichiers de données, et en re-créant tout le moteur de jeu autour.
                  http://www.openjill.org/doku.php

                  En tout cas, j'espere que tu garde la motivation, et que tu documente tout ce que tu trouve!

                  Commentaire


                  • #24
                    Bonjour les amis, ca fait longtemps.

                    Enfin voila, ce post ne concerne pas Ultima VI (desole), mais Ultima IV !!!
                    J'ai uploade les sources decompiles du jeu sur "https://github.com/ergonomy-joe/u4-decompiled/"
                    Pour les fans du jeu, pour les fans d'origin, pour les curieux ... mais surtout pour les programmeurs qui ne sont pas allergiques au language C "K&R" !

                    Commentaire


                    • #25
                      Salut ergonomy_joe et bon retour.
                      Merci pour les sources je vais jeter un œil.
                      Dernière modification par karamoon, 20-12-2016, 12h26.

                      Commentaire

                      Chargement...
                      X