[Bug 36486] New: Age of Wushu: frequent display of in-game "Don't speed up!" dialog tip disrupts gameplay (Wine fails kernel32.GetTickCount API entry hook check)
wine-bugs at winehq.org
wine-bugs at winehq.org
Sun May 18 17:07:42 CDT 2014
https://bugs.winehq.org/show_bug.cgi?id=36486
Bug ID: 36486
Summary: Age of Wushu: frequent display of in-game "Don't speed
up!" dialog tip disrupts gameplay (Wine fails
kernel32.GetTickCount API entry hook check)
Product: Wine
Version: 1.7.19
Hardware: x86
OS: Linux
Status: NEW
Severity: normal
Priority: P2
Component: kernel32
Assignee: wine-bugs at winehq.org
Reporter: focht at gmx.net
Hello folks,
during investigation of bug 36465 I found this goodie, deserving it's own bug
of course :)
Searching the net reveals similar reports for Windows and MAC:
http://www.ageofwushu.com/forums/viewtopic.php?f=29&t=7339
http://www.ageofwushu.com/forums/viewtopic.php?f=30&t=14469
http://portingteam.com/topic/9284-age-of-wushu-dont-speed-up-tip-spam/
This disguised message is in fact the result of a failed API hook check.
The vendor obviously doesn't want to go into technical details/reveal this, see
the support forums/site for answers to this problem.
Enter Wine and get the answers :)
One hint is contained in the game log file 'bin/trace.log'
With relay thunks emitted (+relay channel):
--- snip ---
...
[2014-05-18 22:01:13.573] Time Func Pos Begin QueryPerformanceCounter=54
QueryPerformanceFrequency=54 GetTickCount=54 timeGetTime=54 timeSetEvent=54
SetTimer=54
...
--- snip ---
without relay thunks:
--- snip ---
[2014-05-18 22:23:02.818] Time Func Pos Begin QueryPerformanceCounter=8d
QueryPerformanceFrequency=8d GetTickCount=55 timeGetTime=55 timeSetEvent=8d
SetTimer=55
--- snip ---
The hex codes are the first opcode byte of each API entry.
The protection code makes a snapshot of certain API entries on startup.
The entry bytes are stored for later comparison to detect dynamic hooking
(through dll injection for example).
--- snip ---
...
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3b1
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3b1
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eed4 "QueryPerformanceCounter")
ret=1134c3c3
0056:Ret KERNEL32.GetProcAddress() retval=7b824afc ret=1134c3c3
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3d9
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3d9
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eeb4
"QueryPerformanceFrequency") ret=1134c3e5
0056:Ret KERNEL32.GetProcAddress() retval=7b824b14 ret=1134c3e5
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3fb
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eea4 "GetTickCount")
ret=1134c407
0056:Ret KERNEL32.GetProcAddress() retval=7b82374c ret=1134c407
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c41d
0056:Ret KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c41d
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee88 "timeGetTime") ret=1134c429
0056:Ret KERNEL32.GetProcAddress() retval=7b82374c ret=1134c429
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c43f
0056:Ret KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c43f
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee78 "timeSetEvent")
ret=1134c44b
0056:Ret KERNEL32.GetProcAddress() retval=f7226d44 ret=1134c44b
0056:Call KERNEL32.GetModuleHandleA(11a8ee68 "USER32.dll") ret=1134c461
0056:Ret KERNEL32.GetModuleHandleA() retval=7eca0000 ret=1134c461
0056:Call KERNEL32.GetProcAddress(7eca0000,11a8ee5c "SetTimer") ret=1134c46d
0056:Ret KERNEL32.GetProcAddress() retval=7ecb0798 ret=1134c46d
...
--- snip ---
Runtime check of API entries every two seconds:
--- snip ---
...
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c4f5
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c4f5
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eed4 "QueryPerformanceCounter")
ret=1134c505
0056:Ret KERNEL32.GetProcAddress() retval=7b824afc ret=1134c505
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c2fb
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eeb4
"QueryPerformanceFrequency") ret=1134c30b
0056:Ret KERNEL32.GetProcAddress() retval=7b824b14 ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c2fb
0056:Ret KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eea4 "GetTickCount")
ret=1134c30b
0056:Ret KERNEL32.GetProcAddress() retval=7b82374c ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c32b
0056:Ret KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c32b
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee88 "timeGetTime") ret=1134c33b
0056:Ret KERNEL32.GetProcAddress() retval=7b82374c ret=1134c33b
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c2fb
0056:Ret KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee78 "timeSetEvent")
ret=1134c30b
0056:Ret KERNEL32.GetProcAddress() retval=f7226d44 ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8ee68 "USER32.dll") ret=1134c2fb
0056:Ret KERNEL32.GetModuleHandleA() retval=7eca0000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7eca0000,11a8ee5c "SetTimer") ret=1134c30b
0056:Ret KERNEL32.GetProcAddress() retval=7ecb0798 ret=1134c30b
...
--- snip ---
Following is the list of API functions and their opcode checks.
call/jmp opcode bytes are treated as hook.
--- snip ---
kernel32.dll!QueryPerformanceCounter
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
kernel32.dll!QueryPerformanceFrequency
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
kernel32.dll!GetTickCount
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
WINMM.dll!timeGetTime
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot> | [7]=0xE8 | [7]=0xE9 |
[7]=0xFF
WINMM.dll!timeSetEvent
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
user32.dll!SetTimer
-> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
--- snip ---
The 'WINMM.dll!timeGetTime' entry point gets a special treatment and this the
problem.
Wine 'winmm.spec'
--- snip ---
@ stdcall timeGetTime() kernel32.GetTickCount
--- snip ---
Forwarded. Dumping the target with Winedbg gives:
--- snip ---
Wine-dbg>x/10b GetTickCount
0x7b8480d9 GetTickCount: 55 89 e5 53 83 e4 f0 e8 ab 75
--- snip ---
Disassembly:
--- snip ---
7B8480D9 55 PUSH EBP
7B8480DA 89E5 MOV EBP,ESP
7B8480DC 53 PUSH EBX
7B8480DD 83E4 F0 AND ESP,FFFFFFF0
7B8480E0 E8 AB75FDFF CALL KERNEL32.__x86.get_pc_thunk.bx
7B8480E5 81C3 1B2F0700 ADD EBX,72F1B
7B8480EB E8 72FFFFFF CALL KERNEL32.GetTickCount64
7B8480F0 8B5D FC MOV EBX,DWORD PTR SS:[EBP-4]
7B8480F3 C9 LEAVE
7B8480F4 C3 RETN
--- snip ---
*eeek* .. 'entry[7]' has indeed value 0xE8 hence the check fails.
The PIC code (setup of PIC register) in function prolog causes the harm here.
If you avoid the call opcode at 'entry[7]' everything is fine (for example
making this entry hotpatchable, inline GetTickCount64, use wrapper).
I already tested this. Wine code is no longer (mis)detected as hook and the
game runs fine without any "Speedup" spam.
$ sha1sum AgeofWushu_download.exe
a7101c50ce8deb33008da4ce044fca5e3add721d AgeofWushu_download.exe
$ du -sh AgeofWushu_download.exe
1.9M AgeofWushu_download.exe
$ wine --version
wine-1.7.19
Regards
--
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.
More information about the wine-bugs
mailing list