Hack Age of Empires III pour modifier les paramètres de qualité du shader

Début mai 2020 - si vous êtes comme moi, alors la quarantaine vous a fait refaire des jeux qui n'avaient pas été lancés depuis de nombreuses années.

Et si vous êtes encore plus comme moi, alors vous pourriez avoir un disque d'Age of Empires 3. Peut-être que vous jouez sur un Mac, peut-être que vous n'avez pas encore mis à niveau vers Catalina et que vous souhaitez commander Morgan Black.

Donc, vous démarrez le jeu, entrez dans le menu principal et remarquez immédiatement que quelque chose ne va pas ... Le menu a l'air dégoûtant .


Si vous vous demandez ce qu'est exactement «dégoûtant», alors faites attention à l'eau. Tout le reste est terrible aussi, mais c'est moins évident.


Donc, vous allez dans les options, augmentez tous les paramètres au maximum ... Mais le jeu est toujours moche.

Vous remarquez que les options « Shader Quality » sont étrangement verrouillées sur « Low ».

Tentative n ° 1 - pirater les paramètres


À ce stade, vous commencez à rechercher le dossier du jeu, qui aurait été créé quelque part dans le dossier Documents, car le jeu était en 2005, puis tout le monde l'a fait.

Que cherchons-nous? Bien sûr, le fichier dans lequel les paramètres sont stockés. L'interface de menu ne nous permet pas de modifier les paramètres, mais nous sommes délicats, non?

Nous trouvons le XML requis, car le jeu est 2005 et c'est, bien sûr, XML, où nous tombons sur l'option " optiongrfxshaderquality ", qui est définie sur 0. Il semble que nous le cherchions, alors nous augmentons la valeur à 100, car il n'y a pas beaucoup de qualité. Ici, je suis d'accord avec vous.


Vous pouvez également remarquer qu'il s'agit d'une utilisation terrible de XML. Heureusement, il n'a pas de bonnes utilisations.

Satisfait de nous-mêmes, nous lançons le jeu. Hélas, rien n'a changé. En regardant à nouveau le fichier XML, nous voyons que le paramètre est à nouveau réglé sur 0. Ensemble Studios (qu'il repose en paix) regarde avec mépris toute votre ingéniosité.

Après cela, nous pourrions abandonner . Il s'agit d'un Mac, par conséquent, les correctifs écrits par les utilisateurs sont peu susceptibles d'être trouvés (en fait, j'ai vérifié - il existe toutes sortes de solutions douteuses qui peuvent fonctionner). Tout tourne autour des graphismes. Il semble que le problème sera difficile à résoudre.

Mais nous n'abandonnerons pas. Parce que nous nous souvenons à quoi devrait ressembler l'Âge 3. Nous nous souvenons de la façon dont nous admirions cette belle eau ondulée. Ces effets de particules. Ces énormes navires qui ne rentrent tout simplement pas dans le cadre. Et c'est juste que la caméra a été zoomée, à quoi pensais-je juste?

Tentative n ° 1 - piratage de données


Au tout début du dossier dans Documents, il y a un fichier journal. Le jeu y a écrit une carte graphique et a signalé qu'il avait sélectionné le paramètre «dx7 générique». Il indique que vous avez installé Intel GMA 950, qui était vraiment sur l'ordinateur précédent.

Mais là-dessus, vous avez Intel Iris Graphics 6100. Quelque chose ne va vraiment pas. Vous faites une hypothèse - le jeu détermine les capacités graphiques de l'ordinateur, en le comparant avec une base de données de cartes graphiques au lieu de vérifier les capacités de la carte elle-même. Parce que c'est exactement ce que font les développeurs AAA, et si vous avez travaillé sur RTS open-source , vous savez comment cela se produit.


Le contenu du fichier journal. Si vous trouvez drôle qu'Intel soit désigné comme 0x8086, alors vous avez choisi l'article approprié.

Par hasard, vous trouvez d'anciennes notes de mise à jour qui confirment vos préoccupations. Vous regardez dans le dossier GameData, mais il n'y a que des fichiers .bar et .xmb qui sont pour la plupart illisibles. Vous recherchez grep " Intel " et " dx7 ". Rien ne se passe. Supprimer plusieurs fichiers ... Rien ne se passe. Mais vous savez que les jeux AAA peuvent stocker des ressources dans des endroits étranges, et c'est le portage des jeux Windows sur Mac, donc tout peut être très bizarre ici.

En fin de compte, vous trouvez le fichier «render.bar», si vous le déplacez, le jeu plante au démarrage. Ce n'est pas très clair, mais pas mal. Nous ouvrons le fichier dans Hex Fiend , car ce programme vous permet généralement d'apprendre quelque chose sur les fichiers binaires. Et il s'avère donc. Une partie de ces données est du texte lisible. Certains d'entre eux sont des shaders. Mais la plupart des données sont étrangement divisées par un espace vide ...


Interface Hex Fiend C'est minimaliste, mais ça marche. Les premiers octets sont les lettres «ESPN», qui sont très probablement l'en-tête .bar.

Vous vous exclamez: "Évidemment, c'est UTF-16!" Le jeu a été créé pour Windows, il est donc logique qu'il stocke du texte en UTF-16, gaspillant près de la moitié d'un fichier de données de 30 mégaoctets. Après tout, Windows adorait l'UTF-16 (bien qu'il ne devrait pas ), alors pourquoi ne pas utiliser cet encodage et ces shaders ASCII dans un seul fichier. C'est une idée logique!

(En fait, il m'a fallu environ un jour pour le comprendre)

Hex Fiend a la possibilité d'analyser les octets comme UTF-16, donc nous recherchons à nouveau la chaîne grep dx7. Il y a plusieurs entrées. Nous les remplaçons par la ligne dx9, qui se trouve également dans les données, enregistrons le fichier et lancons le jeu.

Ouais De nouveau, les journaux n'affichent pas "dx9". Cela se situe probablement ailleurs.

Essayons autre chose. Supposons que le jeu reconnaisse les cartes graphiques et que leurs identificateurs hexadécimaux soient stockés dans les journaux (dans mon cas, 0x8086 est Intel, et également 0x2773). Nous recherchons «8086» dans Hex Fiend, nous trouvons quelque chose et une partie du texte semble prometteuse. L'Intel 9 Series est la série GMA 950, et Age 3 n'a probablement pas très bien fonctionné.


Certains numéros sont comme des identifiants de carte (2582, 2592); peut-être que si vous les remplacez, quelque chose changera. Nous avons quand même fait une copie du fichier, vous pouvez donc essayer.

Hélas, c'est aussi une impasse. En fait, nous étudions le mauvais dossier. Heureusement, je peux vous dire cela parce que j'y ai passé beaucoup de temps et que j'ai trop essayé . Cette fichue chose a pris environ 20 heures. Et cela ne compte pas le temps pour toutes les captures d'écran ... Le

fichier souhaité s'appelle "DataP.bar", et si nous remplaçons judicieusement l'ID, nous verrons quelque chose de complètement nouveau dans les journaux:


Je ne comprends pas très bien pourquoi ce «dx7 générique» n'est pas le même que dans la capture d'écran ci-dessus.

Bien sûr, dans le jeu, nous sommes toujours limités par les paramètres «bas», c'est-à-dire que les journaux ne mentent pas. Nous espérions un Medium, mais hélas .

Il semble que plus de piratage de données ne puisse être réalisé. Il est temps de prendre les outils au sérieux.

Tentative n ° 2 - piratage


Donc, si rien ne réussit, il nous reste une dernière chose: changer le fichier exécutable lui-même (c'est-à-dire, comme ils l'ont dit en 2005, «le casser»). Il semble qu'aujourd'hui, il soit simplement appelé piratage, mais ce terme a été conservé dans le domaine du piratage de jeux (bien sûr, je ne l'ai jamais fait personnellement).

Nous passons à la partie la plus intéressante de l'article (oui, vous avez déjà lu mille mots, mais n'était-ce pas curieux?).

Trémie de démontage


A ce stade, notre outil sera un désassembleur: une application qui reçoit le code binaire du fichier exécutable et le transforme en code assembleur lisible (ha ha). Le plus populaire d'entre eux est IDA Pro, mais il est incroyablement cher et je ne sais pas si cela fonctionne sur un Mac, donc j'utiliserai Hopper . Ce n'est pas gratuit (coûte 90 $), mais il peut être utilisé librement pendant 30 minutes à la fois avec presque toutes les fonctions nécessaires.

Je ne vais pas expliquer l'interface en détail, car tout est bien présenté dans le tutoriel Hopper , mais je vais vous dire ce que je vais faire et essayer de prendre suffisamment de captures d'écran pour que vous puissiez apprendre quelque chose.

Vous devez d'abord dire: il m'a fallu cinq jours pour résoudre, et de nombreuses tentatives infructueuses ont été faites. Fondamentalement, je parlerai des étapes réussies, donc cela semblera magique. Mais c'était difficile . Ne vous inquiétez pas si vous avez des problèmes.

Et encore une remarque: le «cracking» est le plus souvent illégal et / ou effectué à des fins illégales. Je démontre un exemple assez rare d'une utilisation assez légitime, qui en même temps est complètement «chapeau blanc», donc tout est en ordre ici. Je suppose que, à strictement parler, cela est illégal / contraire à l'accord de l'utilisateur final, mais, bien sûr, il s'agit d'un cas de force majeure . Quoi qu'il en soit, payez ce que vous aimez, et seulement pour cela, car l'anti-consumérisme est cool.




Faites glisser l'application Age 3 pour l'ouvrir. Sélectionnez simplement "x86" au lieu de powerPC, car le jeu date de 2005.

Étape 1 - recherchez le fragment souhaité


Étonnamment, cela peut être le plus difficile. Nous avons un code désassemblé, mais nous ne savons pas où chercher. Oui, certains jeux ont des symboles de débogage, vous pouvez donc lire les noms des fonctions, mais pas dans notre cas. Hopper nous donne des noms comme "sub_a8cbb6", que nous devons gérer seuls.

Heureusement, nous avons une longueur d'avance: notre fichier journal . Il est très probable que des littéraux de chaîne soient utilisés lors de l'écriture dans le journal, c'est-à-dire que l'ASCII est flashé dans le fichier exécutable. Nous pouvons les rechercher avec grep, car nous ne nous en débarrasserons par aucune compilation (cependant, ils peuvent être cachés par un obscurcissement de code. Heureusement, cela ne s'est pas produit.). La recherche de littéraux de chaîne grep est généralement la première chose qu'ils font lors du démontage d'un programme. Alors, saisissons «vendeur trouvé» dans la boîte de recherche de la trémie et il trouvera quelque chose:


Oh oui, les littéraux à cordes, je les adore.

En soi, cela ne nous a pas tellement avancés. Mais comme l'adresse de cette "chaîne de littéraux" est codée en dur, nous pouvons y trouver des liens. Hopper les a surlignés en vert à droite: "DATA XREF = sub_1d5908 + 1252." Double-cliquez sur la ligne, nous allons passer à notre procédure hero - sub_1d5908 .


Ici, nous trouvons le code assembleur. On pense qu'il est difficile à lire, mais ce n'est pas le cas. Le plus difficile est de le comprendre.

Par défaut, Hopper utilise la "syntaxe Intel", c'est-à-dire que le premier opérande est le récepteur et le second la source. Dans la ligne en surbrillance, nous voyons mov dword [esp+0x1DC8+var_1DC4], aXmlRenderConfi. Faisons-le.

Notre première ligne d'assembleur


movEst une équipe MOV connue pour être sur x86 Turing-complete . Il déplace (copie en fait) les données de A vers B, ce qui signifie essentiellement lire A et écrire dans B. Sur la base de ce qui précède, aXmlRenderConfic'est A, et dword [esp+0x1DC8+var_1DC4]c'est B, le destinataire. Examinons cela de plus près.

aXmlRenderConfi- c'est la valeur que Hopper nous aide. Il s'agit en fait d'un alias pour l'adresse mémoire du littéral de chaîne sur lequel nous avons cliqué il y a quelques secondes. Si vous divisez la fenêtre et regardez le code en mode Hex (qui est le mode préféré des hackers), nous le verrons 88 18 83 00.


Hopper met en évidence les mêmes fragments sélectionnés dans les deux fenêtres.

Si la valeur «flip» est correcte, nous obtiendrons 0x00831888 - l'adresse mémoire du littéral de chaîne (dans l'une des captures d'écran ci-dessus, elle est même surlignée en jaune). Ainsi, une énigme est résolue: le code exécute MOV, c'est-à-dire qu'il écrit l'adresse 0x00831888.

Pourquoi est-il écrit comme ça 88 18 83 00, pas comment 00 83 18 88? Cela s'est produit en raison de l' ordre des octets - un sujet plutôt déroutant qui a généralement peu d'impact. C'est l'une de ces choses qui font dire aux gens que «les ordinateurs ne fonctionnent pas». Pour nous, cela signifie que ce problème se produira dans tous les nombres avec lesquels nous travaillerons aujourd'hui.

Vous remarquerez peut-être que j'ai dit "écrivez l'adresse", et c'est vrai: nous ne nous soucions pas vraiment du contenu, qui s'est avéré être un littéral de chaîne avec un octet zéro à la fin. Nous notons l'adresse car le code fera plus tard référence à cette adresse. Ceci est un pointeur.

Et alors dword [esp+0x1DC8+var_1DC4]? Ici, tout est plus compliqué. dwordsignifie que nous travaillons avec des "mots doubles (mots doubles)", c'est-à-dire avec 16 * 2 bits. Autrement dit, nous copions 32 bits. Rappel: notre adresse est0x00831888, c'est-à-dire 8 caractères hexadécimaux, et chaque caractère hexadécimal peut avoir 16 valeurs, soit 4 bits de données. Nous obtenons donc 8 * 4 = 32. Soit dit en passant, la notation «32 bits» pour les systèmes informatiques est venue d'ici. Si nous utilisions 64 bits, l'adresse mémoire serait écrite comme 0x001122334455667788, serait deux fois plus longue et 2 ^ 32 fois plus grande, et nous devions copier 16 octets. Nous aurions donc beaucoup plus de mémoire, mais la copie du pointeur nécessiterait deux fois plus de travail, et donc 64 bits ne sont en fait pas deux fois plus rapides que 32 bits. Soit dit en passant, une explication plus détaillée du terme «mot» peut être trouvée ici . Parce que c'est, bien sûr, plus compliqué que mon explication.

Alors, qu'en est-il de la partie entre crochets? Les crochets signifient que nous calculons ce qui est à l'intérieur et le considérons comme un pointeur. Par conséquent, la commande MOV écrira à l'adresse mémoire obtenue à partir des calculs. Examinons-le à nouveau plus en détail (et ne vous inquiétez pas, lorsque vous aurez terminé, vous saurez essentiellement lire le code assembleur avec la syntaxe Intel).

espc'est un registre qui dans l'assembleur est l'équivalent le plus proche d'une variable. Il existe de nombreux registres dans l’architecture x86, et différents registres ont des modes d’application différents, mais cela nous importe peu. Apprenez-en plus ici.. Sachez simplement qu'il existe des registres spéciaux pour les nombres à virgule flottante, ainsi que des «indicateurs» qui sont utilisés pour les comparaisons et les opérations similaires. Tout le reste n'est que des nombres hexadécimaux que Hopper a retravaillé un peu pour nous aider. var_1DC4- ceci -0x1DC4, nous pouvons le voir au début de la procédure, ou dans le panneau de droite. Cela signifie que le résultat du calcul [esp+0x1DC8+var_1DC4]sera [esp + 0x1DC8 — 0x1DC4]égal à [esp + 4]. Essentiellement, cela signifie «prendre la valeur 32 bits du registre ESP, ajouter 4 et interpréter le résultat comme une adresse mémoire».

Donc, nous répétons: nous écrivons l'adresse du littéral de chaîne en «esp + 4». Ce sont des informations correctes, mais elles sont complètement inutiles. Qu'est-ce que ça veut dire?

C'est la difficulté de lire le code assembleur: en analysant ce qu'il doit faire. Ici, nous avons une longueur d'avance: nous savons qu'il écrit très probablement quelque chose dans le journal. Par conséquent, nous pouvons nous attendre à un appel à une fonction qui écrit dans le journal. Cherchons pour elle.


Dans la capture d'écran ci-dessus, il existe plusieurs appels similaires, ainsi que la commande callqui appelle la procédure. Hopper en parle généralement (je ne sais pas pourquoi il ne l'a pas fait ici), mais en substance, nous donnons des arguments pour appeler la fonction. Si vous cliquez sur différents appels de sous-programme, nous trouvons quelque chose appelé imp___jump_table___Z22StringVPrintfExWorkerAPcmmPS_PmmPKcS_. C'est le nom décodé de la fonction, et la partie «Printf» suffit pour comprendre qu'elle sort vraiment quelque chose. Peu nous importe comment elle le fait.

Prendre du recul


À ce stade, nous nous sommes complètement écartés de ce que nous avons fait: nous avons essayé de convaincre le jeu que notre carte graphique 2015 était suffisamment puissante pour lancer le jeu 2005. Résumons. Nous avons trouvé l'endroit où le journal est enregistré. Si nous avons de la chance, voici le code qui vérifie les paramètres et les capacités des cartes graphiques. Heureusement, nous avons vraiment eu de la chance (il y a de fortes chances que sinon cet article ne l'aurait pas été).

Pour avoir une vue d'ensemble, nous utiliserons la fonction Hopper Control Flow Graph, qui divise le code en petits blocs et trace des flèches indiquant différentes transitions. C'est beaucoup plus facile à comprendre que dans un assembleur ordinaire, dans lequel souvent tout est en désordre.


L'appel à l'entrée du journal est au milieu d'une fonction terriblement longue, car cela se produit généralement dans les jeux AAA. Ici, nous devons rechercher d'autres littéraux et "astuces" de chaîne Hopper, dans l'espoir de trouver quelque chose. Au début de la procédure se trouve le reste de la journalisation, et ci-dessous il y a des parties intéressantes sur les options de «forçage dx7» et «Très élevé», ainsi qu'un problème avec la version des pixel shaders ... que nous avons.

Le journal de nos données "piratées" a rapporté que nous avions des pixel shaders de la version 0.0, et ils sont incompatibles avec les paramètres "High". Ce code est situé dans le coin inférieur gauche de notre fonction, et si vous regardez attentivement, vous pouvez voir quelque chose de curieux (mis en évidence par votre humble serviteur): il s'agit du même code répété quatre fois pour les paramètres "Très élevé", "Haut", "Moyen" et "Bas". Et oui, il m'a fallu plusieurs heures pour trouver cela.


J'ai surligné en bleu le bloc dans lequel «pixel shader version 0.0 High» est affiché dans le journal. J'ai mis en évidence des parties similaires avec d'autres belles couleurs.

Les seules différences sont que nous écrivons «0x0», «0x1», «0x2» et «0x3» à différents endroits. Ceci est étrangement similaire au fichier de paramètres que nous avons examiné ci-dessus et au paramètre optiongrfxshaderquality (vous ne le comprenez peut-être pas encore. Mais il l'est, croyez-moi).

À ce stade, nous pouvons essayer d'aller de différentes manières, ce que j'ai fait. Mais je serai bref - nous ferons le plus nécessaire: découvrez pourquoi la vérification du pixel shader échoue. Cherchons une branche. Le bloc de sortie du journal est linéaire, ce qui signifie qu'il devrait être quelque part au-dessus.


Les fans de SEMVER, voir un format de version plus avancé: les nombres à virgule flottante.

Et en fait, juste au-dessus, il y a jbune commande de transition (pensez à «goto»). Il s'agit d'un " saut conditionnel " et la condition est " si moins ". Les «ifs» de l'assembleur fonctionnent via des indicateurs de registre , dont j'ai brièvement parlé ci-dessus. Il suffit de savoir que nous devons regarder la commande ci-dessus: très probablement, elle définit le drapeau. Il s'agit souvent d'une commande cmpou test, mais utilisée ici ucomiss. C'est de la barbarie. Mais elle google facilement: ucomiss . Il s'agit d'une commande de comparaison à virgule flottante, ce qui explique pourquoi le code axmm0: Il s'agit d'un registre de nombres à virgule flottante. C'est logique: les journaux indiquent que notre version des pixel shaders est «0.0», qui est une valeur à virgule flottante. Alors, qu'est-ce qui se trouve à l'adresse mémoire 0x89034c? Les données hexadécimales ont un aspect 00 00 00 40et elles peuvent être déroutantes. Mais nous savons que nous devons nous attendre à des nombres à virgule flottante, alors interprétons les données comme ça: cela signifie 2.0. Qui est une valeur logique à virgule flottante pour la version des pixel shaders que Microsoft a réellement utilisée dans son langage d'ombrage de haut niveausur lequel les shaders DirectX sont écrits. Et Age 3 est un jeu DirectX, même si le port sur Mac utilise OpenGL. Il est également logique qu'en 2005, des pixel shaders de la version 2.0 soient nécessaires pour activer le paramètre High, nous allons donc dans la bonne direction.

Comment réparons nous ça? Cette instruction de comparaison effectue une comparaison avec la valeur à xmm0laquelle est donnée dans la ligne ci - dessus: movss xmm0, dword [eax+0xA2A8]. MOVSS est une commande spéciale de «déplacement» pour les registres à virgule flottante, et nous écrivons 32 bits à partir de l'adresse, qui est le résultat de l'évaluation de eax + 0xA2A8.

Vraisemblablement, cette valeur n'est pas de 2,0, mais plutôt de 0,0. Corrigeons-le.

Faire du vrai piratage


Enfin, nous sommes prêts à accéder aux paramètres du jeu High. Nous voulons suivre le chemin "rouge" et sauter toute la logique avec la "mauvaise version des pixel shaders". Cela peut être fait en passant avec succès la comparaison, c'est-à-dire en inversant la condition de transition, mais nous avons une astuce de plus: le code est composé de telle manière que lors de la suppression de la transition, nous irons dans le sens "rouge". Remplaçons simplement la transition par nopune opération qui ne fait rien (il est impossible de simplement supprimer ou ajouter des données au fichier exécutable car un décalage se produira et tout se cassera).

Je recommande de quitter CFG pour cela, car sinon Hopper commence à devenir fou. Si tout est fait correctement, alors dans la fenêtre Hex, nous verrons des lignes rouges.



Le rouge montre les changements que nous avons apportés. À ce stade, si vous avez payé Hopper, vous pouvez simplement enregistrer le fichier. Mais je n'ai pas payé, je vais donc ouvrir le fichier exécutable dans Hex Fiend, trouver la zone souhaitée (en copiant la zone de Hopper dans l'onglet Rechercher à l'intérieur de Hex Fiend) et la modifier manuellement. Faites attention ici, vous pouvez tout casser, donc je recommande de copier quelque part le fichier exécutable source.

Cela fait, lancez le jeu, allez dans les options ... Et bravo - nous pouvons sélectionner "High". Mais on ne peut pas choisir "moyen". Et nous ne pouvons pas utiliser les paramètres élevés des ombres. Néanmoins, nous progressons!


Jetez un œil à ces magnifiques reflets dans l'eau!

Améliorations


En fait, vous pouvez résoudre ce problème de la meilleure façon. Notre solution a fonctionné, mais nous pouvons faire en sorte que la condition soit remplie correctement: faire croire au jeu que notre carte graphique a en fait des shaders de la version 2.0.

Comme nous nous en souvenons, le jeu charge la valeur à l'adresse eax+0xA2A8. Vous pouvez utiliser Hopper pour découvrir ce qui a été enregistré à l'origine. Nous devons trouver une commande dans laquelle 0xA2A8 est utilisé comme opérande destinataire (j'ai choisi 0xA2A8 parce que c'est une valeur assez spécifique, et nous ne pouvons pas être sûrs que le même registre n'est pas utilisé ailleurs). Ici, le rétro-éclairage de la trémie nous est très utile, car nous pouvons simplement cliquer sur 0xA2A8 et rechercher les parties jaunes dans CFG.


Notre victoire est un peu plus élevée: comme prévu, nous écrivons la valeur d'une sorte de registre à virgule flottante. J'avoue que je ne comprends pas vraiment ce qui se passe avant cela (ma meilleure supposition: le jeu vérifie la possibilité de compression de texture), mais ce n'est pas particulièrement important. Écrivons «2.0» et continuons le travail.

Pour ce faire, nous utiliserons les «instructions d'assemblage» de l'application Hopper, puis effectuerons les mêmes manipulations dans Hex Fiend.




Nous utilisons MOV au lieu de MOVSS car nous écrivons la valeur habituelle, et non la valeur spéciale du registre à virgule flottante. De plus, il est en ordre d'octets direct, c'est-à-dire inversé.

Vous remarquerez qu'il se passe quelque chose d'étrange: nous avons «mangé» l'équipe jb. Cela est arrivé parce que la nouvelle commande prend plus d'octets que la précédente, et comme je l'ai dit, nous ne pouvons pas ajouter ou supprimer des données du fichier exécutable pour enregistrer les décalages. Par conséquent, Hopper n'a d'autre choix que de détruire l'équipe jb. Cela peut devenir un problème et nous pouvons l'éliminer en déplaçant la commande vers le haut (après tout, nous n'avons plus besoin d'écrire la valeur xmm3). Cela fonctionnera ici, car dans tous les cas, nous faisons le branchement sur le bon chemin. Chanceux.

Si vous démarrez le jeu ... alors rien ne changera beaucoup. J'ai suggéré que la série Intel 9 n'est pas assez rapide, donc le jeu se plaint. Ce n'est pas un problème, car en réalité nous nous efforçons de «très élevé».

Quelle était la carte la plus puissante autour de 2004? NVIDIA GeforceFX 6800. Voyons comment convaincre le jeu que nous l'avons installé.

Dans le trou de lapin


Oui, c'est une toute nouvelle section.

Nous savons que le jeu utilise des codes Hex pour désigner les cartes graphiques et qu'il reçoit des informations des fichiers de données. Nous savons que cela devrait se produire dans la fonction que nous étudions (au moins ce serait étrange si ce n'était pas le cas). Nous pouvons donc trouver le code qui fait cela. Mais cela peut encore être difficile, car nous ne savons pas exactement quoi chercher.

Nous avons un indice: les options se comportent étrangement; il y a Low / High, mais pas de Medium. Il y a donc peut-être un autre code qui gère tous ces «très haut», «haut», «moyen» et «bas». Et il est en fait, dans la même fonction. Il est beaucoup plus tôt au CFG et semble intéressant.


Nous pouvons supposer que les données brutes sont stockées dans une sorte de XML, car il est très probable, car il existe plusieurs autres fichiers de données qui sont XML. Et XML est essentiellement un tas de pointeurs et d'attributs. Par conséquent, le code ici vérifie très probablement certains attributs XML (et en fait, ci-dessous est «attendu un ID, très élevé ...», ce qui est très similaire à la vérification de l'exactitude du fichier). Nous pouvons imaginer une structure XML similaire dans laquelle les valeurs booléennes déterminent la qualité des shaders:


Il convient de considérer qu'il s'agit d'une supposition très arbitraire. Tout peut sembler un peu différent.

Une partie très intéressante est affichée sur le côté gauche du CFG (pour vous, cela peut être à droite, mais à gauche dans la capture d'écran): nous voyons le même var_CCqui est utilisé dans le code qui écrit l'ID de l'appareil dans le journal. Autrement dit ebp+var_CC, se réfère probablement à l'ID de notre appareil, 0x2773. Ce qui est étrange, c'est qu'il n'y a pas de littéral de chaîne dans la première «vérification», mais il s'agit d'un bogue Hopper: l'adresse 0x0089da8e contient des données hexadécimales 69006400, qui en UTF-16 signifie «id» (en fait, probablement Hopper n'a pas pu comprendre cela à cause de UTF16). Et nous cherchons juste une pièce d'identité.

Vous savez quoi? Lançons le débogueur et vérifions tout dans la réalité.

Débogage du jeu


Ouvrez une fenêtre de terminal et exécutez lldb (tapez simplement lldb et appuyez sur Entrée). Nous devons d'abord dire à LLDB de suivre l'âge de 3 ans, puis nous pouvons commencer le jeu en appelant run. Le jeu démarre et nous n'obtenons rien d'utile.


Maintenant, nous devons ajouter un point d'arrêt: pour confirmer nos hypothèses, nous voulons arrêter l'exécution lorsque nous arrivons au code indiqué ci-dessus. Nous n'avons pas de code source, mais cela n'a pas d'importance: nous pouvons définir un point d'arrêt à l'adresse mémoire. Et nous avons l'adresse dans le code. Ajoutons un arrêt à un appel MOV qui reçoit un "id", son adresse est 0x001d5e68. Pour ajouter un point d'arrêt, entrez simplement br set -a 001D5E68. Après avoir démarré le jeu, il s'arrête et LLDB affiche le code désassemblé (dans la syntaxe AT&T, pas Intel, donc tous les opérandes sont inversés, mais on voit que c'est le même code). Vous pouvez remarquer que LLDB dans ce cas est en fait plus intelligent que Hopper; il nous dit que nous effectuons des opérations liées à XML et à la sortie printf.



Envie d'un hacker?

Pour continuer, prenons une «instruction par étapes». Une commande courte pour cela
est ni. En répétant plusieurs fois, nous remarquons que nous revenons à notre point de départ. Ceci est un cycle! Et c'est tout à fait logique: nous itérons probablement autour d'une sorte de XML. En fait, Hopper nous l'a montré - si nous suivons le CFG, nous verrons un cycle (possible).


Cela signifie:if (*(ebp+var_CC) == *(ebp+var_28)) { *(ebp+var_1D84)=edi }

Sa condition de sortie est que la valeur ebp+var_1D84soit différente de zéro. Et nous voyons un paramètre de code intéressant dans celui mis en évidence par moi var_CC.

Ajoutons un point d'arrêt sur celui-ci cmpet nous le découvrirons.



Gauche: définissez un point d'arrêt sur le CMP et poursuivez l'exécution. A droite, nous surveillons les registres et la mémoire.

Nous regardons les registres avec reg ret la valeur de la mémoire avec mem read $ebp-0x28. eaxet contient réellement 0x2773, et la valeur en mémoire est maintenant 0x00A0. Si vous l'exécutez plusieurs fois thread c, nous verrons que l'itération se produit sur différents ID à la recherche de correspondances. À un certain stade, les valeurs coïncideront, la boucle se terminera et le jeu commencera. Maintenant, nous savons comment le gérer.

Rappelez-vous le fichier journal: il a identifié à la fois l'appareil et le fabricant. Quelque part, il y a probablement un cycle similaire pour les noms des fabricants. Et en fait, il est très similaire et situé dans le CFG directement au-dessus de notre cycle. Cette boucle est un peu plus simple, et nous recherchons la chaîne «vendor».

Cette fois, la comparaison est effectuée avecvar_D0. Cette variable est en fait utilisée comme ID fabricant. Si nous mettons un point d'arrêt ici et vérifions tout, nous verrons le 0x8086 familier à eax, et en même temps, une comparaison avec 0x10DE se produit. Écrivons-le eaxet voyons ce qui se passe.



Sensationnel.

Autrement dit, 0x10DE est NVIDIA. En fait, on pourrait comprendre cela en étudiant un fichier de données, mais ce n'est pas très intéressant. Maintenant, la question est: quel est l'identifiant du GeforceFX 6800? Nous pouvons utiliser LLDB et vérifier simplement chaque identifiant, mais cela prendra du temps (il y en a beaucoup, j'ai essayé de trouver le bon). Jetons donc un coup d'œil à render.bar cette fois.


Supposons qu'il s'agisse de «XMB», la représentation binaire de XML utilisée à l'age 3.

Ce fichier est assez chaotique. Mais après les balises Geforce FX 6800, nous voyons plusieurs identifiants. Très probablement, nous en avons besoin. Essayons 00F1.

Le moyen le plus simple de tester cela est d'utiliser LLDB et de définir les points d'arrêt souhaités. Je vais sauter cette étape, mais je dirai que 00F1 est apparu (heureusement).

À ce stade, nous devons répondre à la question: «Comment rendre ce changement permanent?» Il semble qu'il sera plus facile de changer les valeurs var_CCet var_D0sur 0X00F1 et 0X10DE. Pour ce faire, nous avons juste besoin d'avoir de l'espace pour le code.

Une solution simple serait de remplacer l'un des appels d'enregistrement de journal par un NOP, par exemple, un appel de sous-système. Cela nous libérera plusieurs dizaines d'octets, et c'est encore plus que nécessaire. Sélectionnons tout cela et remplaçons-le par NOP. Ensuite, nous avons juste besoin d'écrire des informations en utilisant les variables MOV: assembleur mov dword [ebp+var_CC], 0xF1et mov dword [ebp+var_D0], 0x10DE.



Avant et après

Lançons le jeu. Enfin, nous avons réalisé quelque chose: vous pouvez choisir entre Faible, Moyen ou Élevé, ainsi qu'activer les paramètres Élevés pour les ombres.


Mais nous ne pouvons toujours pas activer Très élevé, et c'est triste. Que ce passe-t-il?


Ah, j'ai compris.

Il s'est avéré que le Geforce FX 6800 devrait prendre en charge la version 3.0 pixel shaders, et nous ne définissons que la version 2.0. Maintenant, il est facile pour nous de le réparer: il suffit d'écrire 3.0 en virgule flottante ebp+0xA2A8. Cette valeur est 0x40400000.

Enfin, le jeu n'est plus capricieux, et nous pouvons profiter de paramètres très élevés.


Étrange, mais ça a l'air complètement différent. Les couleurs sont beaucoup moins vibrantes et un brouillard dépendant de la plage est apparu. Il y a aussi un petit problème de conflit d'ombres (il est également dans les captures d'écran ci-dessus, cela est dû aux paramètres d'ombre élevés, pas à la qualité des shaders), que je n'ai pas encore pu résoudre.

Et c'est la fin de notre voyage, merci pour la lecture.

De plus, je donnerai des exemples de l'apparence de chacun des paramètres du jeu, car toutes les captures d'écran trouvées en ligne sont terribles ou incomplètes.

Les graphismes sur Medium sont tout à fait acceptables, mais souffrent d'un manque d'ombres. Pour autant que je sache, très élevé ajoute pour la plupart une LUT complètement différente (une bonne explication de ce terme peut être trouvée dans la section Gradation des couleurs de cet article.), du brouillard au loin et peut-être même un modèle d'éclairage complètement différent? L'image est très différente et c'est plutôt étrange.





De haut en bas: très élevé, élevé (avec les options d'ombre élevée), moyen (juste avec l'ombre activée), faible (avec l'ombre désactivée).

Autres liens utiles


Le blog que j'ai lié ci-dessus contient une merveilleuse analyse des pipelines de rendu modernes: http://www.adriancourreges.com/blog/

Rate 0 AD est un jeu RTS open-source du même genre: https://play0ad.com . Elle a besoin de développeurs.

Si vous voulez en savoir plus sur le format XMB, vous pouvez lire son code en 0 AD . Je n'ai pas vérifié, mais je suis sûr que c'est le même format, car la personne qui a écrit le décodeur XMB pour Age 3 sur Age of Empires heaven a également implémenté ces fichiers dans le code source 0 AD en 2004. Donc, ce sera une leçon amusante en paléontologie de code. Merci pour tout le travail, Ykkrosh.

Je me souviens exactement que j'ai lu un excellent tutoriel sur l'utilisation de Hopper, mais maintenant je ne trouve pas le lien. Si quelqu'un sait de quoi je parle, supprimez le lien.

Au final, je voudrais mentionner le projet Cosmic Frontier: un remake de la série classique Escape Velocity (plus d'Override, mais il est également compatible avec Nova). C'est un jeu incroyable et il est très triste que peu de gens le connaissent. Maintenant, les créateurs ont lancé une campagne sur Kickstarter, et le développeur principal a un blog de développement très intéressant , qui parle d'utiliser Hex Fiend pour inverser les formats de données du jeu original. Ceci est une excellente lecture. Si vous pensez que mon article était fou. puis dans l'un des postes, ils:

  • Nous avons lancé l'émulateur Mac classique pour utiliser les API obsolètes depuis longtemps pour lire un fichier de données crypté.
  • Cryptage d'ingénierie inverse à partir du code du jeu
  • Ils ont utilisé un hack de système de fichiers pour accéder aux mêmes données sur un Mac moderne.

Oui, ce sont des gens vraiment passionnés, et j'espère que leur projet sera mené à bien, car EV Nova est mon jeu préféré.

All Articles