Modifikasi ke game berdasarkan dll-wrapper'a

Ada permainan In Verbis Virtus dengan mekanik yang tidak biasa - gunakan mantra menggunakan mikrofon.

Ini bukan simulator Hmayak Hakobyan, ini adalah puzzle orang pertama dengan kontrol atipikal.
Untuk ini, gim ini menggunakan perpustakaan pengenal ucapan Sphinx.

Idenya terlihat menarik, tetapi implementasinya keluar begitu-begitu (pengakuan sangat sering meleset), dan terus terang menjengkelkan untuk dilemparkan setelah 20 menit pertama.
Tentang bagaimana tampilannya dari luar - umumnya diam.

Para pengembang, sayangnya, tidak meninggalkan kemampuan untuk mengontrol mantra dari keyboard, dan saya memutuskan untuk memperbaikinya.

Pikiran pertama adalah membuat perubahan pada perpustakaan Sphinx, karena ini adalah open-source. Namun, saya menemukan bahwa ada banyak versi perpustakaan ini.

Setelah mencoba tiga dari mereka (kira-kira sesuai dengan waktu permainan itu dirilis), saya masih tidak dapat menemukan yang benar, karena masing-masing memiliki perbedaan (setidaknya dalam hal set fungsi yang diekspor).

Karena itu, saya memutuskan untuk membuat pembungkus di atas perpustakaan asli dari permainan.

Untuk melakukan ini, saya mengambil pendekatan yang diusulkan dalam artikel Generating .DLL Wrappers .

Intinya adalah bahwa Anda dapat membungkus pustaka apa pun tanpa sepengetahuan parameter dan jenis fungsi yang diekspor, hanya nama mereka (yang dapat diekstraksi bahkan dengan editor teks) sudah cukup.

Daftar ekspor dibuat menggunakan def-file dari form:

EXPORTS func1=_func1 @1 func2=_func2 @2 

Fungsi pembungkus itu sendiri adalah:

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

Ini menghilangkan masalah lewat argumen dan mengembalikan nilai fungsi asli.

Pertama, sedikit teknik terbalik diperlukan. Saya membuat pembungkus dengan satu-satunya tambahan - mencatat nama fungsi yang dipanggil.

Jadi saya menentukan di mana, kapan dan bagaimana logika perpustakaan inti bekerja.

Ternyata pada awalnya sejumlah sampel mentah dari mikrofon dikumpulkan oleh fungsi ps_process_raw (), dan kemudian keputusan itu sendiri dibuat dalam fungsi ps_get_hyp ().
Kemudian (terlambat), saya masih berpikir akan lebih baik melihat dokumentasi Sphinx terlebih dahulu (di mana semuanya dijelaskan).

Diputuskan untuk menambahkan ke fungsi ps_process_raw () definisi status kunci yang akan bertanggung jawab untuk mantra.

Untuk melakukan ini, Anda perlu menetapkan kunci-kunci ini. Kami melakukan ini di DllMain (), bersama dengan mendapatkan alamat dari fungsi aslinya. Berikut beberapa iklan:

 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; } 

File settings.ini memiliki bentuk:

 [main] 49=String 1 50=String 2 

Total, dalam array buf akan ada garis yang sesuai dengan mantra. Selain itu, mereka akan terletak pada indeks yang sesuai dengan kunci yang diperlukan.

Kami akan menentukan status kunci sebagai berikut:

 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; } } 

Wrapper dari fungsi ps_process_raw () akan terlihat seperti ini:

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

Artinya, jika, pada saat diperlukan untuk memasukkan ke dalam mikrofon, pengguna menekan tombol, penunjuk ke garis yang sesuai dengan tombol yang ditekan disimpan dalam variabel global i.

Persiapan sudah selesai, sekarang saatnya untuk mengimplementasikan fungsionalitas dasar.

Penting untuk menentukan apakah tombol ejaan ditekan oleh pengguna, dan jika demikian, ubah nilai balik di fungsi ps_get_hyp ().

Ini akan memerlukan sedikit manipulasi tumpukan:

  _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 } } 

Fungsionalitas utama adalah sepotong dengan komentar "ganti hasil (jika tombol ditekan)".
Jika pointer ada dalam variabel global, kami mengganti hasil yang dikembalikan dan mereset variabel global.

Dan jika tidak, maka kita membiarkan semuanya tidak berubah.

Dengan demikian, Anda dapat terus memutar melalui mikrofon, atau Anda dapat menggunakan tombol (mereka memiliki prioritas). Tujuan tercapai.

Ya, ada titik bengkok dalam solusi.

Misalnya, melewatkan pointer melalui variabel global, juga disebut i (saya memutuskan untuk menggunakannya lagi setelah inisialisasi di DllMain).

Mendaki tumpukan orang lain juga entah bagaimana tidak diterima (saya tidak berpikir bagaimana melakukannya secara berbeda).

Namun, solusinya cukup berhasil. Kode utama kurang dari 100 baris, sebagian besar, semuanya sepele.

Kode sumber
file def
File biner + pengaturan

Instalasi:

  • Dalam folder \ In Verbis Virtus \ Binaries \ Win32 \, ganti nama pocketsphinx.dll asli menjadi pocketsphinx_orig.dll
  • Letakkan pocketsphinx.dll pembungkus terdekat
  • Dalam folder \ In Verbis Virtus \ Binaries \ Win32 \ UserCode, masukkan settings.ini

Kritik dan saran diterima.

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


All Articles