奇迹智能英语记忆系统3.0真人发音,比原来的TTS可是好了N多。但其中一些单词还是只有TTS发音,而且句子全是TTS,而且用的是MS的TTS发音,真是惨不忍听。如果能采用NEOSPEECH发音,就和真人差不多了。
一开始,做了个可以用NEOSPEECH发音的ttd.exe程序,然后,修改奇迹智能原代码,将TTS发音的调用改为SHELL调用:形式为ttd.exe string,string为传入的参数(就是要读出的单词或句子),由ttd.exe再调用sapi发音。但这种SHELL方式每发音一次就要调用一次ttd.exe,速度比较慢。因此,想做成DLL调用。可以先做一个DLL,然后在奇迹智能中调用就行。DLL好做,但如何在奇迹智能中调用呢?
查了论坛上的资料,增加DLL调用的方法有两种,一种是PE工具增加improt,一种是在程序中直接调用。但是找不到VB程序中增加DLL调用的方法。
于是自己在程序中找找了,看看有没有相关的代码。
正好程序中有一个打开网站的SHELL调用,就先跟踪它吧!
用dream写的VB事件断点插件对全部事件下断,(为全部事件下断的方法:VB程序分析方法及修改例 (“万能分析断点”、启动form修改,form隐藏))很快就跟踪到SHELL调用:相关代码如下:
0044FD08 . 73 68 65 6C 6C 33 32 2E 64 6C 6C 00 ascii "shell32.dll",0 要调用的DLL的ASCII
0044FD18 . 53 68 65 6C 6C 45 78 65 63 75 74 65 41 00 ascii "ShellExecuteA",0 要调用的函数的ASCII
0044FD26 00 db 00
0044FD27 00 db 00
0044FD28 08FD4400 dd luosoft_.0044FD08 ; ASCII "shell32.dll" 指向DLL的地址
0044FD2C 18FD4400 dd luosoft_.0044FD18 ; ASCII "ShellExecuteA" 指向函数的地址
0044FD30 00 db 00
0044FD31 00 db 00
0044FD32 00 db 00 有四个00
0044FD33 00 db 00
0044FD34 D46B6100 dd luosoft_.00616BD4 地址指针,在00616BD4+8中存放函数的调用地址
0044FD38 00 db 00
0044FD39 00 db 00
0044FD3A 00 db 00
0044FD3B 00 db 00
0044FD3C 00 db 00
0044FD3D 00 db 00
0044FD3E 00 db 00
0044FD3F 00 db 00
0044FD40 $ A1 DC6B6100 mov eax, [616BDC] 将00616BD4+C中的函数调用地址放EAX
0044FD45 . 0BC0 or eax, eax
0044FD47 . 74 02 je short 0044FD4B 函数调用地址是否为空
0044FD49 . FFE0 jmp eax 不为空,直接调用函数
0044FD4B > 68 28FD4400 push 0044FD28 函数调用地址为空时,压入DLL指针
0044FD50 . B8 D02C4000 mov eax, <jmp.&MSVBVM60.DllFunctionCall>
0044FD55 . FFD0 call eax 调用VB库得到函数调用指针,指针放入00616BD4+8=616BDC
0044FD57 . FFE0 jmp eax 调用函数
要注意的是,
0044FD28 08FD4400 dd luosoft_.0044FD08 ; ASCII "shell32.dll" 指向DLL的地址
0044FD2C 18FD4400 dd luosoft_.0044FD18 ; ASCII "ShellExecuteA" 指向函数的地址
044FD30 00 db 00
0044FD31 00 db 00
0044FD32 00 db 00 有四个00
0044FD33 00 db 00
0044FD34 D46B6100 dd luosoft_.00616BD4
这一段数据很重要,一是指向DLL及函数的指针,还有就是四个00及其后的函数地址存放地址。开始时,我没有指定函数存放地址,结果调用就出错。
需要注意的是0044FD50 . B8 D02C4000 mov eax, <jmp.&MSVBVM60.DllFunctionCall>
<jmp.&MSVBVM60.DllFunctionCall>究竟是什么呢?
我们来看看VB程序开头,可以找到如下一连串jmp,仔细看看,就能找到<jmp.&MSVBVM60.DllFunctionCall>的地址了:00402CD0
00402CB8 .- FF25 F0104000 jmp [<&MSVBVM60._CIsin>] ; MSVBVM60._CIsin
00402CBE .- FF25 5C114000 jmp [<&MSVBVM60._CIsqrt>] ; MSVBVM60._CIsqrt
00402CC4 .- FF25 94124000 jmp [<&MSVBVM60._CItan>] ; MSVBVM60._CItan
00402CCA .- FF25 84124000 jmp [<&MSVBVM60._allmul>] ; MSVBVM60._allmul
00402CD0 .- FF25 30114000 jmp [<&MSVBVM60.DllFunctionCall>] ; MSVBVM60.DllFunctionCall
00402CD6 .- FF25 4C114000 jmp [<&MSVBVM60.__vbaRedim>] ; MSVBVM60.__vbaRedim
00402CDC .- FF25 90104000 jmp [<&MSVBVM60.__vbaVarXor>] ; MSVBVM60.__vbaVarXor
00402CE2 .- FF25 38104000 jmp [<&MSVBVM60.__vbaVarIdiv>] ; MSVBVM60.__vbaVarIdiv
00402CE8 .- FF25 E8114000 jmp [<&MSVBVM60.__vbaVarInt>] ; MSVBVM60.__vbaVarInt
现在就可以增加自己的DLL调用了。按以上代码形式,找个空地,将DLL和函数改为自己的。然后在调用DLL的程序中加上调用就行了:
push 参数 这个参数是你的DLL函数要用到的参数,我的这个程序就是string的地址,只有一个参数,所以用一个PUSH就行了。
call 0044FD40
附:回顾一下一般程序中增加DLL调用的方法: http://bbs.pediy.com/showthread.php?s=&threadid=17365&highlight=%BC%C7%CA%C2%B1%BE
0100878B push 0100873C ; /pModule = "USER32.dll"
01008790 call [<&KERNEL32.GetModuleHandleA>] ; \GetModuleHandleA
01008796 push 01001344 ; /ProcNameOrOrdinal = "AppendMenuA"
0100879B push eax ; |hModule
0100879C call [<&KERNEL32.GetProcAddress>] ; \GetProcAddress
010087A2 call eax
原来是没看懂,现在明白了:
这是先push要调用的DLL的地址,然后调用GetModuleHandleA,而call [<&KERNEL32.GetModuleHandleA>]的代码也是应从程序中找到。
call [<&KERNEL32.GetProcAddress>] 也是一样的。
后来发现,采用[<&KERNEL32.GetModuleHandleA>]调用的DLL,是在程序启动时已加载了的。
如果要调用启动时未加载的DLL,必须用call <jmp.&kernel32.LoadLibraryA>
即:
0100878B push 0100873C ; /pModule = "USER32.dll"
01008790 call [<&KERNEL32.LoadLibraryA>] ; \LoadLibraryA
01008796 push 01001344 ; /ProcNameOrOrdinal = "AppendMenuA"
0100879B push eax ; |hModule
0100879C call [<&KERNEL32.GetProcAddress>] ; \GetProcAddress
010087A2 call eax
MSVBVM60.DllFunctionCall中用的就是LoadLibraryA