Verbis Virtus中有一款游戏具有异常的机制-使用麦克风施放咒语。
这不是Hmayak Hakobyan的模拟器,这是具有非典型控制的第一人称拼图。
为此,游戏使用Sphinx语音识别库。
这个主意看起来很有趣,但是实现却是马马虎虎(人们经常会认错),并且坦率地说,在开始的20分钟之后,演员就很讨厌。
从外部看它的外观-通常保持沉默。
不幸的是,开发人员没有离开通过键盘控制拼写的能力,我决定修复它。
首先想到的是对Sphinx库进行更改,因为它是开源的。 但是,我发现该库有很多版本。
在尝试了其中三个(大致对应于游戏发布的时间)之后,我仍然找不到合适的一个,因为每个都有任何差异(至少在导出函数集方面)。
因此,我决定在游戏的原始库的顶部进行包装。
为此,我采用了“
生成.DLL包装器 ”一文中提出的方法。
其本质是您可以包装任何库而无需任何有关导出函数的参数和类型的知识,仅它们的名称(即使使用文本编辑器也可以提取它们)就足够了。
导出列表是使用以下格式的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 {
主要功能是带有注释“替换结果(如果按下键)”的片段。
如果指针位于全局变量中,我们将替换返回的结果并重置全局变量。
如果没有,那么我们将一切保持不变。
因此,您可以继续投射麦克风,也可以使用按钮(它们具有优先级)。 目标已实现。
是的,解决方案存在歪点。
例如,将指针传递给也称为i的全局变量(在DllMain中初始化后,我决定再次使用它)。
攀登别人的栈也是不被接受的(我没有想到如何做不同的事情)。
但是,该解决方案非常有效。 主代码少于100行,在大多数情况下,所有内容都是微不足道的。
源代码def文件二进制+设置文件安装方式:
- 在\在Verbis Virtus \ Binaries \ Win32 \文件夹中,将原始的pocketsphinx.dll重命名为pocketsphinx_orig.dll
- 将附近的包装器pocketsphinx.dll
- 在\在Verbis Virtus \ Binaries \ Win32 \ UserCode文件夹中,放入settings.ini
批评和建议被接受。