游戏分析之消息处理
结合反汇编代码分析示例游戏的 Windows 消息处理,介绍窗口创建、绘图、键盘输入、激活和退出消息如何组织游戏运行。
从上篇中的注册窗口类的时候可以看出消息处理函数的地址为00402318。从这里也可以看出不太像VC,je,jl的一大堆。
流程概述
- WM_CREATE消息,申请空间创建内存图像和游戏绘图DC,以及画出开场的画面。
- WM_DESTORY消息,释放空间恢复和释放游戏绘图DC,发送退出消息。
- WM_ACTIVATE消息,调整程序的优先级。
- WM_PAINT消息,及时刷新屏幕。
- WM_KEYDOWN,根据不同的按键调用不同的功能,比如控制游戏
- WM_KEYUP,和WM_KEYDOWN相反,主要用来平衡WM_KEYDOWN的控制游戏
流程分析
1. WM_CREATE
进入事件call
004025BF |. 68 28040000 push 428 /HeapSize = 428 (1064.)
004025C4 |. 6A 00 push 0 |Flags = 0
004025C6 |. E8 21220000 call <jmp.&KERNEL32.GetProcessHeap> |[GetProcessHeap
004025CB |. 50 push eax |hHeap
004025CC |. E8 15220000 call <jmp.&KERNEL32.HeapAlloc> \HeapAlloc
004025D1 |. 8BF0 mov esi, eax
004025D3 |. BA 28000000 mov edx, 28
004025D8 |. 8BC6 mov eax, esi
004025DA |. E8 75FBFFFF call 00402154
004025DF |. C706 28000000 mov dword ptr [esi], 28
004025E5 |. 33C9 xor ecx, ecx
004025E7 |. C746 04 40010>mov dword ptr [esi+4], 140
004025EE |. C746 08 F0000>mov dword ptr [esi+8], 0F0
004025F5 |. 66:C746 0C 01>mov word ptr [esi+C], 1
004025FB |. 66:C746 0E 08>mov word ptr [esi+E], 8
00402601 |. 894E 10 mov dword ptr [esi+10], ecx
00402604 |. 33DB xor ebx, ebx
00402606 |. C746 20 10000>mov dword ptr [esi+20], 10
0040260D |. 8D46 2A lea eax, dword ptr [esi+2A]
00402610 |> 8BD3 mov edx, ebx
00402612 |. 8BCA mov ecx, edx
00402614 |. 80E1 01 and cl, 1
00402617 |. C1E1 07 shl ecx, 7
0040261A |. 8808 mov byte ptr [eax], cl
0040261C |. 8BCB mov ecx, ebx
0040261E |. 83E1 01 and ecx, 1
00402621 |. 8BE9 mov ebp, ecx
00402623 |. C1E1 08 shl ecx, 8
00402626 |. 2BCD sub ecx, ebp
00402628 |. 8848 20 mov byte ptr [eax+20], cl
0040262B |. 8BCA mov ecx, edx
0040262D |. 80E1 02 and cl, 2
00402630 |. 80E2 04 and dl, 4
00402633 |. C1E1 07 shl ecx, 7
00402636 |. 8848 FF mov byte ptr [eax-1], cl
00402639 |. 8BCB mov ecx, ebx
0040263B |. 83E1 02 and ecx, 2
0040263E |. 8BE9 mov ebp, ecx
00402640 |. C1E1 08 shl ecx, 8
00402643 |. 2BCD sub ecx, ebp
00402645 |. C1E2 07 shl edx, 7
00402648 |. 8848 1F mov byte ptr [eax+1F], cl
0040264B |. 8850 FE mov byte ptr [eax-2], dl
0040264E |. 8BD3 mov edx, ebx
00402650 |. 83E2 04 and edx, 4
00402653 |. 43 inc ebx
00402654 |. 8BCA mov ecx, edx
00402656 |. C1E1 08 shl ecx, 8
00402659 |. 2BCA sub ecx, edx
0040265B |. 8848 1E mov byte ptr [eax+1E], cl
0040265E |. 83C0 04 add eax, 4
00402661 |. 83FB 08 cmp ebx, 8
00402664 |.^\7C AA jl short 00402610
00402666 |. C646 46 C0 mov byte ptr [esi+46], 0C0
0040266A |. C646 45 C0 mov byte ptr [esi+45], 0C0
0040266E |. C646 44 C0 mov byte ptr [esi+44], 0C0
00402672 |. C646 4A 80 mov byte ptr [esi+4A], 80
00402676 |. C646 49 80 mov byte ptr [esi+49], 80
0040267A |. C646 48 80 mov byte ptr [esi+48], 80申请堆空间,并在堆空间里写入了bitmap的头信息。
00402681 |. 50 push eax /hWnd
00402682 |. E8 CB210000 call <jmp.&USER32.GetDC> \GetDC
00402687 |. 8BE8 mov ebp, eax
00402689 |. 6A 00 push 0 /Offset = 0
0040268B |. 6A 00 push 0 |hSection = NULL
0040268D |. 68 FC694000 push 004069FC |ppBits = game.004069FC
00402692 |. 6A 00 push 0 |Usage = DIB_RGB_COLORS
00402694 |. 56 push esi |pBitmapInfo
00402695 |. 55 push ebp |hDC
00402696 |. E8 3B220000 call <jmp.&GDI32.CreateDIBSection> \CreateDIBSection
0040269B |. A3 EC694000 mov dword ptr [4069EC], eax
004026A0 |. 68 F0000000 push 0F0 /Height = F0 (240.)
004026A5 |. 68 40010000 push 140 |Width = 140 (320.)
004026AA |. 55 push ebp |hDC
004026AB |. E8 32220000 call <jmp.&GDI32.CreateCompatibleBitm> \CreateCompatibleBitmap
004026B0 |. A3 F4694000 mov dword ptr [4069F4], eax
004026B5 |. 56 push esi /pMemory
004026B6 |. 6A 00 push 0 |Flags = 0
004026B8 |. E8 2F210000 call <jmp.&KERNEL32.GetProcessHeap> |[GetProcessHeap
004026BD |. 50 push eax |hHeap
004026BE |. E8 1D210000 call <jmp.&KERNEL32.HeapFree> \HeapFree根据以上的头信息创建内存的位图,绘图的内存首地址指针放在0x004069F4
|||||||||||||||||||||||||||||||||||||||
在游戏开场画面上写上文字、贴上位图。
2. WM_DESTORY
00402986 |. 50 push eax /pMemory => NULL
00402987 |. 6A 00 push 0 |Flags = 0
00402989 |. E8 5E1E0000 call <jmp.&KERNEL32.GetProcessHeap> |[GetProcessHeap
0040298E |. 50 push eax |hHeap
0040298F |. E8 4C1E0000 call <jmp.&KERNEL32.HeapFree> \HeapFree
00402994 |. 33D2 xor edx, edx
00402996 |. 8915 006A4000 mov dword ptr [406A00], edx
0040299C |> 6A FF push -1 /nSavedDC = FFFFFFFF (-1.)
0040299E |. 8B0D E0694000 mov ecx, dword ptr [4069E0] |
004029A4 |. 51 push ecx |hDC => NULL
004029A5 |. E8 081F0000 call <jmp.&GDI32.RestoreDC> \RestoreDC
004029AA |. 6A FF push -1 /nSavedDC = FFFFFFFF (-1.)
004029AC |. A1 E4694000 mov eax, dword ptr [4069E4] |
004029B1 |. 50 push eax |hDC => NULL
004029B2 |. E8 FB1E0000 call <jmp.&GDI32.RestoreDC> \RestoreDC
004029B7 |. 6A FF push -1 /nSavedDC = FFFFFFFF (-1.)
004029B9 |. 8B15 E8694000 mov edx, dword ptr [4069E8] |
004029BF |. 52 push edx |hDC => NULL
004029C0 |. E8 ED1E0000 call <jmp.&GDI32.RestoreDC> \RestoreDC释放所申请的堆空间和游戏绘图DC
、、、、、、、、、、、、、、、、、、、、、、、、、、、、
00402A1F |. 6A 00 push 0 /ExitCode = 0
00402A21 |. 8915 906D4000 mov dword ptr [406D90], edx |
00402A27 |. E8 081E0000 call <jmp.&USER32.PostQuitMessage> \PostQuitMessage发送消息通知退出
3. WM_ACTIVATE
00402394 |> \B8 01000000 mov eax, 1
00402399 |. E8 2E230000 call 004046CC注意上面有为eax传参的操作,跟进call 004046CC会发现:
004046DA |. 85C0 test eax, eax
004046DC |. 74 6D je short 0040474B首先判断eax,然后有分支:
004046F5 |. 6A 01 push 1 /Show = TRUE
004046F7 |. E8 26010000 call <jmp.&USER32.ShowCursor> \ShowCursor
004046FC |. 8B0D CC6D4000 mov ecx, dword ptr [406DCC]
00404702 |. 85C9 test ecx, ecx
00404704 |. 74 0D je short 00404713
00404706 |. 6A 20 push 20 /Priority = NORMAL_PRIORITY_CLASS
00404708 |. E8 F1000000 call <jmp.&KERNEL32.GetCurrentProcess> |[GetCurrentProcess
0040470D |. 50 push eax |hProcess
0040470E |. E8 C7000000 call <jmp.&KERNEL32.SetPriorityClass> \SetPriorityClass
00404713 |> 6A 00 push 0 /Priority = THREAD_PRIORITY_NORMAL
00404715 |. E8 DE000000 call <jmp.&KERNEL32.GetCurrentThread> |[GetCurrentThread
0040471A |. 50 push eax |hThread
0040471B |. E8 B4000000 call <jmp.&KERNEL32.SetThreadPriority> \SetThreadPriority
00404720 |. 68 2000CC00 push 0CC0020 /ROP = SRCCOPY
00404725 |. 6A 00 push 0 |YSrc = 0
00404727 |. 6A 00 push 0 |XSrc = 0
00404729 |. A1 E0694000 mov eax, dword ptr [4069E0] |
0040472E |. 50 push eax |hSrcDC => NULL
0040472F |. 68 F0000000 push 0F0 |Height = F0 (240.)
00404734 |. 68 40010000 push 140 |Width = 140 (320.)
00404739 |. 6A 00 push 0 |YDest = 0
0040473B |. 6A 00 push 0 |XDest = 0
0040473D |. 8B15 E4694000 mov edx, dword ptr [4069E4] |
00404743 |. 52 push edx |hDestDC => NULL
00404744 |. E8 9F010000 call <jmp.&GDI32.BitBlt> \BitBlt这些代码的意思是把鼠标显示出来然后把游戏进程的优先级调低(比如说游戏暂停或窗口失去焦点的时候),另一个分支和此完全相反,而本消息适合于另一分支。后面调用这个函数的时候很多。
4. WM_PAINT
用BeginPaint和EndPaint做了简单的刷新。
5. WM_KEYDOWN
提取按键信息,比如游戏控制按键,将按键和全局变量[406D7C]以不同的变量作或运算。
如果按键为ESC的话
00402431 |> \B8 01000000 mov eax, 1 Case 1B of switch 004023C2
00402436 |. E8 91220000 call 004046CC
0040243B |. 6A 06 push 6 /ShowState = SW_MINIMIZE
0040243D |. 8B55 08 mov edx, dword ptr [ebp+8] |
00402440 |. 52 push edx |hWnd
00402441 |. E8 D6230000 call <jmp.&USER32.ShowWindow> \ShowWindow就降低游戏程序的优先级,并最小化。
如果按键为ENTER时
0040244B |> \8B0D 846D4000 mov ecx, dword ptr [406D84] Cases D,20 of switch 004023C2
00402451 |. 83E9 01 sub ecx, 1 Switch (cases 0..6)
00402454 |. 72 33 jb short 00402489
00402456 |. 74 0D je short 00402465这里会把一个全局变量减1并判断,这个全局变量是用来做回车暂停的计数的,从下文的程序里可以看出当暂停11次后就自动死亡,同时它也是用来判断游戏的当前状态的(活动中还是暂停)
进去:
004042F7 |. 6A 00 push 0 /Show = FALSE
004042F9 |. E8 24050000 call <jmp.&USER32.ShowCursor> \ShowCursor把鼠标隐藏。
继续call 00404660进去:
00404660 /$ B8 106E4000 mov eax, 00406E10
00404665 |. 33D2 xor edx, edx
00404667 |> C640 08 FF /mov byte ptr [eax+8], 0FF
0040466B |. 83C0 0F |add eax, 0F
0040466E |. 42 |inc edx
0040466F |. 81FA 2C010000 |cmp edx, 12C
00404675 |.^ 7C F0 \jl short 00404667
00404677 |. 8B0D C06D4000 mov ecx, dword ptr [406DC0]把00406E10为首地址的内存每15个字节在偏移为8的位置上写入FF。
其实这个内存就是来存储子弹的信息的。
00404677 |. 8B0D C06D4000 mov ecx, dword ptr [406DC0]
0040467D |. 83E9 01 sub ecx, 1 Switch (cases 0..3)
00404680 |. /72 0A jb short 0040468C
00404682 |. |74 14 je short 00404698
00404684 |. |49 dec ecx
00404685 |. |74 1D je short 004046A4
00404687 |. |49 dec ecx
00404688 |. |74 26 je short 004046B0
0040468A |. |EB 0C jmp short 00404698
0040468C |> \C705 A86D4000>mov dword ptr [406DA8], 1E
00404696 |. EB 22 jmp short 004046BA
00404698 |> C705 A86D4000>mov dword ptr [406DA8], 32 Default case of switch 0040467D
004046A2 |. EB 16 jmp short 004046BA
004046A4 |> C705 A86D4000>mov dword ptr [406DA8], 64 Case 2 of switch 0040467D
004046AE |. EB 0A jmp short 004046BA
004046B0 |> C705 A86D4000>mov dword ptr [406DA8], 0C8 Case 3 of switch 0040467D根据406DC0里的数据来给406DA8赋予不同的值。其实在后面的程序中可以看出406DC0里的东西就是子弹数量(可能也是难度的一个标识)的标识。而地址406DA8里放的是子弹的数量。
最后是对一些全局变量的初始化
返回(这个函数可以看作为游戏的初始化过程)
往后是对游戏各个地方的初始化,包括场景,各种标记常量。