In Verbis Virtus gibt es ein Spiel mit ungewöhnlicher Mechanik - Zauber mit einem Mikrofon wirken.
Dies ist kein Simulator von Hmayak Hakobyan, sondern ein Ego-Puzzle mit atypischen Steuerelementen.
Dafür verwendet das Spiel die Sphinx-Spracherkennungsbibliothek.
Die Idee sieht interessant aus, aber die Implementierung hat sich als mittelmäßig herausgestellt (die Erkennung fehlt sehr oft), und es ist ehrlich gesagt ärgerlich, sie nach den ersten 20 Minuten zu besetzen.
Wie es von außen aussieht - im Allgemeinen still.
Die Entwickler haben leider nicht die Möglichkeit gelassen, Zauber über die Tastatur zu steuern, und ich habe beschlossen, das Problem zu beheben.
Der erste Gedanke war, Änderungen an der Sphinx-Bibliothek vorzunehmen, da diese Open Source ist. Ich habe jedoch festgestellt, dass es eine Reihe von Versionen dieser Bibliothek gibt.
Nachdem ich drei davon ausprobiert hatte (ungefähr entsprechend dem Zeitpunkt, zu dem das Spiel veröffentlicht wurde), konnte ich immer noch nicht das richtige finden, da jedes Unterschiede aufwies (zumindest in Bezug auf die exportierten Funktionen).
Aus diesem Grund habe ich beschlossen, einen Wrapper über der Originalbibliothek des Spiels zu erstellen.
Zu diesem
Zweck habe ich den im Artikel
Generieren von DLL-Wrappern vorgeschlagenen Ansatz gewählt.
Das Wesentliche ist, dass Sie jede Bibliothek ohne Kenntnis der Parameter und Typen exportierter Funktionen umbrechen können. Nur ihre Namen (die auch mit einem Texteditor extrahiert werden können) reichen aus.
Die Exportliste wird mit der Def-Datei des Formulars erstellt:
EXPORTS func1=_func1 @1 func2=_func2 @2
Die Funktions-Wrapper selbst sind:
_declspec(naked) void _func1() { __asm jmp dword ptr [procs + 1 * 4]; }
Dies beseitigt die Probleme der Übergabe von Argumenten und der Rückgabe der Werte der ursprünglichen Funktionen.
Zunächst war ein wenig Reverse Engineering erforderlich. Ich habe einen Wrapper mit dem einzigen Zusatz erstellt - die Namen der aufgerufenen Funktionen zu protokollieren.
Also habe ich festgestellt, wo, wann und wie die Kernbibliothekslogik funktioniert.
Es stellte sich heraus, dass zunächst eine bestimmte Anzahl von Rohproben aus dem Mikrofon von der Funktion ps_process_raw () gesammelt wurde und dann die Entscheidung selbst in der Funktion ps_get_hyp () getroffen wurde.
Später (zu spät) dachte ich immer noch, es lohnt sich, zuerst die Sphinx-Dokumentation zu lesen (wo alles beschrieben wurde).
Es wurde beschlossen, der Funktion ps_process_raw () eine Definition des Status der Schlüssel hinzuzufügen, die für die Zaubersprüche verantwortlich sind.
Dazu müssen Sie diese Schlüssel zuweisen. Wir tun dies in DllMain () und rufen die Adressen der ursprünglichen Funktionen ab. Hier sind einige Werbespots:
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; }
Die Datei settings.ini hat die Form:
[main] 49=String 1 50=String 2
Insgesamt gibt es im Buf-Array Zeilen, die Zaubersprüchen entsprechen. Darüber hinaus werden sie durch Indizes liegen, die den erforderlichen Schlüsseln entsprechen.
Bestimmen Sie den Status der Schlüssel wie folgt:
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; } }
Der Wrapper der Funktion ps_process_raw () sieht folgendermaßen aus:
_declspec(naked) void _ps_process_raw() { find_key(); __asm jmp dword ptr [procs + 78 * 4]; }
Das heißt, wenn der Benutzer zu dem Zeitpunkt, zu dem es erforderlich ist, in das Mikrofon zu werfen, eine Taste gedrückt hat, wurde der Zeiger auf die Zeile, die der gedrückten Taste entspricht, in der globalen Variablen i gespeichert.
Die Vorbereitungen sind abgeschlossen, es ist Zeit, die Grundfunktionalität zu implementieren.
Es muss ermittelt werden, ob der Benutzer die Rechtschreibungstaste gedrückt hat, und in diesem Fall den Rückgabewert in der Funktion ps_get_hyp () geändert werden.
Dies erfordert eine kleine Manipulation des Stapels:
_declspec(naked) void _ps_get_hyp() { static unsigned int return_address; _asm {
Die Hauptfunktionalität ist ein Teil mit dem Kommentar "Ergebnis ersetzen (wenn Taste gedrückt wurde)".
Befindet sich der Zeiger in der globalen Variablen, ersetzen wir das zurückgegebene Ergebnis und setzen die globale Variable zurück.
Und wenn nicht, dann lassen wir alles unverändert.
So können Sie weiterhin durch das Mikrofon werfen oder die Tasten verwenden (sie haben Priorität). Das Ziel ist erreicht.
Ja, die Lösung enthält krumme Punkte.
Beispiel: Übergeben eines Zeigers durch eine globale Variable, auch i genannt (ich habe beschlossen, ihn nach der Initialisierung in DllMain erneut zu verwenden).
Das Klettern auf den Stapel eines anderen wird auch irgendwie nicht akzeptiert (ich habe nicht darüber nachgedacht, wie ich es anders machen soll).
Die Lösung funktioniert jedoch recht gut. Der Hauptcode besteht aus weniger als 100 Zeilen, zum größten Teil ist alles trivial.
Quellcodedef DateiBinär + EinstellungsdateiInstallation:
- Benennen Sie im Ordner \ In Verbis Virtus \ Binaries \ Win32 \ die ursprüngliche Taschenphinx.dll in Taschenphinx_orig.dll um
- Legen Sie in der Nähe Wrapper Taschenphinx.dll
- Geben Sie im Ordner \ In Verbis Virtus \ Binaries \ Win32 \ UserCode die Datei settings.ini ein
Kritik und Vorschläge werden akzeptiert.