تعديل اللعبة بناءً على dll-wrapper'a

هناك لعبة في Verbis Virtus مع ميكانيكا غير عادية - تعاويذ المدلى بها باستخدام ميكروفون.

هذا ليس محاكاة لـ Hmayak Hakobyan ، إنه لغز أول شخص له عناصر تحكم غير نمطية.
لهذا ، تستخدم اللعبة مكتبة التعرف على الكلام Sphinx.

تبدو الفكرة مثيرة للاهتمام ، لكن التنفيذ خرج (كثيرًا ما يفتقد التقدير) ، وبصراحة مزعج للإدلاء به بعد أول 20 دقيقة.
حول كيف تبدو من الخارج - صامتة بشكل عام.

لم يترك المطورون ، للأسف ، القدرة على التحكم في التعاويذ من لوحة المفاتيح ، وقررت إصلاحها.

كانت الفكرة الأولى هي إجراء تغييرات على مكتبة Sphinx ، لأنها مفتوحة المصدر. ومع ذلك ، وجدت أن هناك مجموعة من إصدارات هذه المكتبة.

بعد تجربة ثلاثة منهم (تقريبًا تقريبًا وقت إصدار اللعبة) ، ما زلت لا أجد الخيار الصحيح ، لأن كل واحد منهم كان لديه أي اختلافات (على الأقل فيما يتعلق بمجموعة الوظائف التي تم تصديرها).

لذلك قررت أن ألفها على قمة المكتبة الأصلية من اللعبة.

للقيام بذلك ، اتبعت النهج المقترح في مقالة توليد .DLL Wrappers .

جوهرها هو أنه يمكنك التفاف أي مكتبة دون أي معرفة بالمعلمات وأنواع الوظائف المصدرة ، فقط أسمائهم (والتي يمكن استخراجها حتى مع محرر نصوص) كافية.

يتم إنشاء قائمة التصدير باستخدام ملف def للنموذج:

EXPORTS func1=_func1 @1 func2=_func2 @2 

مغلفة وظيفة أنفسهم:

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

هذا يلغي مشاكل تمرير الوسائط وإرجاع قيم الوظائف الأصلية.

أولا ، كان مطلوبا قليلا من الهندسة العكسية. أنا خلقت المجمع مع الإضافة الوحيدة - تسجيل أسماء الوظائف التي تسمى.

لذلك حددت أين ومتى وكيف يعمل منطق المكتبة الأساسية.

اتضح أنه في البداية تم جمع عدد معين من العينات الخام من الميكروفون بواسطة الدالة ps_process_raw () ، ثم تم اتخاذ القرار نفسه في وظيفة ps_get_hyp ().
في وقت لاحق (بعد فوات الأوان) ، ما زلت أعتقد أنه سيكون من المفيد النظر في وثائق Sphinx أولاً (حيث تم وصفها كلها).

تقرر إضافة وظيفة ps_process_raw () إلى تعريف لحالة المفاتيح التي ستكون مسؤولة عن نوبات.

للقيام بذلك ، تحتاج إلى تعيين هذه المفاتيح. نقوم بذلك في DllMain () ، إلى جانب الحصول على عناوين الوظائف الأصلية. إليك بعض الإعلانات التجارية:

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

يحتوي ملف settings.ini على النموذج:

 [main] 49=String 1 50=String 2 

المجموع ، في صفيف buf سيكون هناك خطوط تقابل تعاويذ. علاوة على ذلك ، فإنهم سوف يكذبون بواسطة مؤشرات المقابلة للمفاتيح اللازمة.

سنحدد حالة المفاتيح كما يلي:

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

سيظهر مجمّع الدالة ps_process_raw () كما يلي:

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

وهذا هو ، إذا كان من الضروري ، في الوقت الذي كان من الضروري فيه الدخول إلى الميكروفون ، ضغط المستخدم على أحد المفاتيح ، وتم حفظ المؤشر إلى السطر المقابل للمفتاح المضغوط في المتغير العام i.

الانتهاء من الاستعدادات ، فقد حان الوقت لتنفيذ الوظائف الأساسية.

من الضروري تحديد ما إذا كان المستخدم قام بالضغط على زر الإملاء ، وإذا كان الأمر كذلك ، فقم بتغيير قيمة الإرجاع في دالة ps_get_hyp ().

سيتطلب ذلك معالجة بسيطة للمكدس:

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

الوظيفة الرئيسية في قطعة مع التعليق "استبدال النتيجة (إذا تم الضغط على المفتاح)".
إذا كان المؤشر في المتغير العام ، فإننا نستبدل النتيجة التي تم إرجاعها ونعيد تعيين المتغير الشامل.

وإذا لم يكن الأمر كذلك ، فإننا نترك كل شيء دون تغيير.

وبالتالي ، يمكنك الاستمرار في بث الميكروفون ، أو يمكنك استخدام الأزرار (لها الأولوية). يتم تحقيق الهدف.

نعم ، هناك نقاط ملتوية في الحل.

على سبيل المثال ، تمرير مؤشر عبر متغير عمومي ، يسمى أيضًا i (قررت استخدامه مرة أخرى بعد التهيئة في DllMain).

تسلق كومة شخص آخر غير مقبول بطريقة أو بأخرى (لم أفكر في كيفية القيام بذلك بطريقة مختلفة).

ومع ذلك ، فإن الحل يعمل تماما. الكود الرئيسي أقل من 100 سطر ، بالنسبة للجزء الأكبر ، كل شيء تافه.

شفرة المصدر
ملف def
ملف إعدادات + ثنائي

التثبيت:

  • في المجلد \ In Verbis Virtus \ Binaries \ Win32 \ ، أعد تسمية pocketsphinx.dll الأصلي إلى pocketsphinx_orig.dll
  • ضع المجمع القريب pocketsphinx.dll
  • في المجلد \ In Verbis Virtus \ Binaries \ Win32 \ UserCode ، ضع settings.ini

يتم قبول النقد والاقتراحات.

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


All Articles