Modification du jeu basée sur dll-wrapper'a

Il y a un jeu dans Verbis Virtus avec des mécanismes inhabituels - lancez des sorts à l'aide d'un microphone.

Ce n'est pas un simulateur de Hmayak Hakobyan, c'est un puzzle à la première personne avec des commandes atypiques.
Pour cela, le jeu utilise la bibliothèque de reconnaissance vocale Sphinx.

L'idée semble intéressante, mais la mise en œuvre est sortie pour ainsi dire (la reconnaissance manque très souvent), et franchement ennuyeuse à lancer après les 20 premières minutes.
À propos de l'apparence extérieure - généralement silencieux.

Les développeurs, malheureusement, n'ont pas laissé la possibilité de contrôler les sorts depuis le clavier, et j'ai décidé de le réparer.

La première pensée a été d'apporter des modifications à la bibliothèque Sphinx, car elle est open-source. Cependant, j'ai trouvé qu'il existe un tas de versions de cette bibliothèque.

Après avoir essayé trois d'entre eux (correspondant à peu près à la date de sortie du jeu), je n'ai toujours pas trouvé le bon, car chacun avait des différences (au moins en termes de l'ensemble des fonctions exportées).

Par conséquent, j'ai décidé de créer un wrapper au-dessus de la bibliothèque d'origine du jeu.

Pour ce faire, j'ai repris l'approche proposée dans l'article Génération de wrappers .DLL .

Son essence est que vous pouvez envelopper n'importe quelle bibliothèque sans aucune connaissance des paramètres et des types de fonctions exportées, seuls leurs noms (qui peuvent être extraits même avec un éditeur de texte) suffisent.

La liste d'exportation est créée à l'aide du fichier def du formulaire:

EXPORTS func1=_func1 @1 func2=_func2 @2 

Les wrappers de fonction eux-mêmes sont:

 _declspec(naked) void _func1() { __asm jmp dword ptr [procs + 1 * 4]; } 

Cela élimine les problèmes de transmission d'arguments et de retour des valeurs des fonctions d'origine.

Tout d'abord, un peu d'ingénierie inverse était nécessaire. J'ai créé un wrapper avec le seul ajout - la journalisation des noms des fonctions appelées.

J'ai donc déterminé où, quand et comment la logique de la bibliothèque principale fonctionne.

Il s'est avéré qu'au début un certain nombre d'échantillons bruts du microphone ont été collectés par la fonction ps_process_raw (), puis la décision elle-même a été prise dans la fonction ps_get_hyp ().
Plus tard (trop tard), j'ai toujours pensé qu'il valait la peine de consulter la documentation Sphinx en premier (où tout était décrit).

Il a été décidé d'ajouter à la fonction ps_process_raw () une définition de l'état des clés qui seront responsables des sorts.

Pour ce faire, vous devez affecter ces clés. Nous le faisons dans DllMain (), en plus d'obtenir les adresses des fonctions d'origine. Voici quelques publicités:

 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { HINSTANCE hinst_dll; if (fdwReason == DLL_PROCESS_ATTACH) { hinst_dll = LoadLibraryA("pocketsphinx_orig.dll"); if (!hinst_dll) return 0; for (i = 0; i < 93; i++) procs[i] = GetProcAddress(hinst_dll, import_names[i]); for (i = 0; i < 256; i++) { _itoa(i, &buf[i][0], 10); GetPrivateProfileStringA("main", &buf[i][0], 0, &buf[i][0], MAX_PATH, ".\\settings.ini"); } i = 0; } else if (fdwReason == DLL_PROCESS_DETACH) FreeLibrary(hinst_dll); return 1; } 

Le fichier settings.ini a la forme:

 [main] 49=String 1 50=String 2 

Total, dans le tableau buf il y aura des lignes correspondant aux sorts. De plus, ils reposeront sur des indices correspondant aux clés nécessaires.

Nous déterminerons l'état des clés comme suit:

 void find_key() { if(!i) { for (i = 0; i < 256; i++) if (buf[i][0]) if (GetAsyncKeyState(i) >> 1) { i = (int)&buf[i][0]; return; } if (i == 256) i = 0; } } 

L'encapsuleur de la fonction ps_process_raw () ressemblera à ceci:

  _declspec(naked) void _ps_process_raw() { find_key(); __asm jmp dword ptr [procs + 78 * 4]; } 

Autrement dit, si au moment où il est nécessaire de lancer dans le microphone, l'utilisateur a appuyé sur une touche - le pointeur vers la ligne correspondant à la touche enfoncée a été enregistré dans la variable globale i.

Les préparatifs sont terminés, il est temps de mettre en œuvre les fonctionnalités de base.

Il est nécessaire de déterminer si le bouton d'orthographe a été enfoncé par l'utilisateur et, dans l'affirmative, de modifier la valeur de retour dans la fonction ps_get_hyp ().

Cela nécessitera une petite manipulation de la pile:

  _declspec(naked) void _ps_get_hyp() { static unsigned int return_address; _asm { //save return address push eax mov eax, dword ptr [esp+4] mov return_address, eax pop eax //call original ps_get_hyp add esp, 4 call dword ptr [procs + 22 * 4] sub esp, 4 //replace result (if key was pressed) cmp i, 0 je end mov eax, i xor ecx,ecx mov i, ecx end: //restore return address push eax mov eax, return_address mov dword ptr [esp+4], eax pop eax ret } } 

La fonctionnalité principale est dans un morceau avec le commentaire "remplacer le résultat (si la touche a été enfoncée)".
Si le pointeur se trouve dans la variable globale, nous substituons le résultat renvoyé et réinitialisons la variable globale.

Et sinon, nous laissons tout inchangé.

Ainsi, vous pouvez continuer à diffuser via le microphone, ou vous pouvez utiliser les boutons (ils ont la priorité). Le but est atteint.

Oui, il y a des points tordus dans la solution.

Par exemple, en passant un pointeur à travers une variable globale, également appelée i (j'ai décidé de l'utiliser à nouveau après l'initialisation dans DllMain).

Grimper la pile de quelqu'un d'autre n'est pas accepté non plus (je n'ai pas pensé à faire autrement).

Cependant, la solution fonctionne très bien. Le code principal est inférieur à 100 lignes, pour la plupart, tout est trivial.

Code source
fichier def
Fichier de paramètres binaire +

Installation:

  • Dans le dossier \ In Verbis Virtus \ Binaries \ Win32 \, renommez pochesphinx.dll d'origine en pochesphinx_orig.dll
  • Mettez le wrapper à proximité pochesphinx.dll
  • Dans le dossier \ In Verbis Virtus \ Binaries \ Win32 \ UserCode, placez settings.ini

Les critiques et suggestions sont acceptées.

Source: https://habr.com/ru/post/fr437884/


All Articles