Modification to the game based on dll-wrapper

    There is a game of In Verbis Virtus with unusual mechanics - to create spells using a microphone.

    This is not a simulator of Amayak Akopyan, this is a first-person puzzle game with atypical controls.
    To do this, the game uses the speech recognition library Sphinx.

    The idea looks interesting, but the implementation came out so-so (recognition often misses), and frankly bored after the first 20 minutes.
    About how it looks from the side - generally keep quiet.

    Developers, unfortunately, did not leave the ability to control spells from the keyboard, and I decided to fix it.

    The first thought was to make changes to the Sphinx library, since it is open-source. However, I found that there are a bunch of versions of this library.

    Having tried three of them (approximately the corresponding release times of the game), I did not find the one I needed, because each had some differences (at least in terms of the set of exported functions).

    Therefore, I decided to make a wrapper over the original library from the game.

    To do this, I used the approach proposed in the article Generating .DLL Wrappers .

    Its essence is that you can wrap any library without any knowledge about the parameters and types of exported functions, just their names (which can be extracted even with a text editor) are sufficient.

    The export list is created using the def file of the form:

    func1=_func1 @1
    func2=_func2 @2

    The function wrappers themselves have the form:

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

    This eliminates problems with passing arguments and returning the values ​​of the original functions.

    For a start, a bit of reverse engineering was required. I created a wrapper with a single addition - logging the names of called functions.

    So I determined where, when and how the main logic of the library works.

    It turned out that at first a certain number of raw samples were collected from the microphone by the function ps_process_raw (), and then the decision itself was made in the function ps_get_hyp ().
    Later (too late), I still thought that at first it would be worth looking at the Sphinx documentation (where it was all described).

    It was decided to add to the ps_process_raw () function a definition of the state of the keys that will be responsible for the spells.

    To do this, you need to assign these keys. We do this in DllMain (), along with getting the addresses of the original functions. Here are the commercials:

    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)
    		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;
    	elseif (fdwReason == DLL_PROCESS_DETACH)

    The settings.ini file is:

    49=String 150=String 2

    So, in the array buf there will be lines corresponding to the spells. And will lie on the indices corresponding to the right keys.

    Determine the state of the keys will be like this:

    		for (i = 0; i < 256; i++)
    			if (buf[i][0])
    				if (GetAsyncKeyState(i) >> 1)
    					i = (int)&buf[i][0];
    		if (i == 256)
    			i = 0;

    The wrapper for the ps_process_raw () function will be:

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

    That is, if at the time when you need to cast into the microphone, the user pressed a key, a pointer to the line corresponding to the pressed key was saved in the global variable i.

    Preparations are finished, it's time to implement the basic functionality.

    It is required to determine whether the user has pressed the spell button, and if so, change the return value in the ps_get_hyp () function.

    This requires a bit of stack manipulation:

     _declspec(naked) void _ps_get_hyp()
    	staticunsignedint return_address;
    		//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
    		//restore return address
    		push eax
    		mov eax, return_address
    		mov dword ptr [esp+4], eax
    		pop eax

    The main functionality is in the piece with the comment “replace result (if key was pressed)”.
    If there is a pointer in the global variable, we substitute the returned result and reset the global variable.

    And if not, then leave everything unchanged.

    Thus, you can continue to cast through the microphone, and you can also use the buttons (they take precedence). The goal is achieved.

    Yes, there are curved moments in the solution.

    For example, passing a pointer through a global variable, and even referred to as i (decided to reuse it after initialization in DllMain).

    Climbing someone else’s stack is also somehow not accepted (I didn’t think how to do otherwise).

    Nevertheless, the solution is quite working. The main code is less than 100 lines, for the most part everything is trivial.

    Source code

    Binary def file + settings file


    • In the \ In Verbis Virtus \ Binaries \ Win32 \ folder, rename the original pocketsphinx.dll to pocketsphinx_orig.dll
    • Put a number of wrappers pocketsphinx.dll
    • Put settings.ini in the \ In Verbis Virtus \ Binaries \ Win32 \ UserCode folder

    Criticism and suggestions are accepted.

    Also popular now: