最近,有朋友总要跟我PK QQ美女找茬,无奈在下眼力实在是不如人。不过,咱可是计算机专业的啊,自己找不过他,还不能利用计算机来找吗?嘿嘿,于是开始研究这个辅助工具。
首先,先看看截图:
下面说说制作的方法。我想,大家应该也能想通制作的方法:获取窗口句柄->找到图片(两张)->对比->设置成不同的颜色->显示出来。
过程是很简单的,先看看第一步,获取窗口句柄
- // 获取游戏句柄
- bool CMain::GetGameHandle(void)
- {
- m_pGame = FindWindow(NULL, _T("大家来找茬"));
- if(m_pGame == NULL)
- return false;
- else
- return true;
- }
这里用到了一个FindWindow()函数,函数原型如下
HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName);
利用窗口名称获取到一个窗口HWND.
然后,很重要的就是获取图片了,把图片要先存入一个缓冲区,以下是代码:
- // 将游戏中位图数据复制到内存中
- bool CMain::CopyPicToBlt(DWORD*& lpvBits, int xSrc, int ySrc, int nWidth, int nHeight)
- {
- HWND hWnd = m_pGame;//主游戏句柄
- HDC hSrcDC=NULL;
- HDC hNewDC;
- //图片格式信息头
- BITMAPINFOHEADER bi;
- bi.biSize = sizeof(BITMAPINFOHEADER);
- bi.biWidth = nWidth;
- bi.biHeight = nHeight;
- bi.biPlanes = 1;
- bi.biBitCount = 32;
- bi.biCompression = BI_RGB;
- bi.biSizeImage = nWidth*nHeight;
- bi.biXPelsPerMeter = 0;
- bi.biYPelsPerMeter = 0;
- bi.biClrUsed = 0;
- bi.biClrImportant = 0;
- HBITMAP pBitmap;
- hSrcDC=GetDC(hWnd);//获取程序DC
- hNewDC = CreateCompatibleDC(hSrcDC);//创建兼容DC
- pBitmap = CreateCompatibleBitmap(hSrcDC, nWidth, nHeight);//设置图大小
- SelectObject(hNewDC, pBitmap); //绑定图片
- //将位图复制到DC中
- BitBlt(hNewDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, SRCCOPY);
- //为图片申请一块内存空间
- if(lpvBits)
- {
- delete[] lpvBits;
- lpvBits=NULL;
- }
- lpvBits = new DWORD[nWidth*nHeight];
- if(!lpvBits)
- {
- return false;
- }
- //将图片数据存储到对应的变量中
- GetDIBits(hNewDC, pBitmap, 0, (UINT)m_nPicHeight, lpvBits, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
- DeleteObject(pBitmap);
- DeleteDC(hNewDC);
- return true;
- }
这里,要先简单说下BMP图片的知识。BMP图片其实在计算机里也是以二进制的方式存储的(任何文件其实都是),这样,我们用一个DWORD指针来作为BUFFER,方便比较。通过这个函数,就把窗口中(xSrc, ySrc)位置的(nWidth, nHeight)大小的位图存储到了lpvBits指向的缓冲区了,以后直接比较两个缓冲区的内容即可。
- // 比较两个图片
- void CMain::CompareBMP(DWORD*& pBuffer, DWORD* pLeft, DWORD* pRight)
- {
- if(!pBuffer)
- {
- delete[] pBuffer;
- pBuffer=NULL;
- }
- pBuffer = new DWORD[m_nPicWidth*m_nPicHeight];
- //比较两幅图,数据相同的设置为白色,不同的为红色。
- for (DWORD i = 0; i < (DWORD)m_nPicWidth * m_nPicHeight; i++)
- {
- if(pLeft[i] != pRight[i])
- pBuffer[i] = RGB(0,0,255);
- else
- pBuffer[i] = RGB(255,255,255);
- }
- //SaveBMP(pBuffer, _T("d://result1.bmp") );
- //转换图片数据,使图片按正常顺序显示。(BMP格式与窗口图像存放竖坐标相反)
- int width = m_nPicWidth;
- for (DWORD i = 0; i < (DWORD)(m_nPicHeight / 2); i++)
- {
- for (DWORD j = 0; j < (DWORD)width; j++)
- {
- DWORD temp;
- temp = *(pBuffer + i * width + j);
- *(pBuffer + i * width + j) = *(pBuffer + width * (m_nPicHeight - 1 - i) + j);
- *(pBuffer + width * (m_nPicHeight - 1 - i) + j) = temp;
- }
- }
- //SaveBMP(pBuffer, _T("d://result2.bmp") );
- if(pLeft)
- {
- delete[] pLeft;
- pLeft = NULL;
- }
- if(pRight)
- {
- delete[] pRight;
- pRight = NULL;
- }
- }
最后通过上面的来比较图片。当然,这个算法是最简单的算法,就是利用循环,按顺序比较,再将结果填充到一个新的缓冲区里,把原先的两张图片缓冲区内内容清除掉
然后,就是显示出来了,首先要先创建一个半透明的对话框
- // 点击查找后
- void CFindFaultDlg::FindFault(void)
- {
- if(!m_GameMain.GetGameHandle())
- {
- MessageBox( _T("请先打开'QQ美女找茬'游戏"), _T("温情提示"));
- return;
- }
- DWORD* pBuffer=NULL;
- //创建一个对话框,用于显示找茬游戏中两幅图的不同点
- if(m_pDlg)
- m_pDlg->DestroyWindow();
- m_GameMain.FindingFault(pBuffer);
- m_pDlg = new CDialog();
- if(m_pDlg)
- {
- if(!m_pDlg->Create(IDD_DLGSHOW, this))
- {
- MessageBox( _T("对话框初始化失败"), _T("温情提示") );
- return ;
- }
- //利用SetLayeredWindowAttributes设置窗口透明。
- //自带的SDK不支持,需要更新才能直接使用这个函数。
- //SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^WS_EX_LAYERED);
- //SetLayeredWindowAttributes(this->m_hWnd,0,128,2);
- //
- //动态的从User32.dll中取得SetLayeredWindowAttributes函数地址。WS_EX_LAYERED = 0x80000
- SetWindowLong(m_pDlg->GetSafeHwnd(),GWL_EXSTYLE, GetWindowLong(m_pDlg->GetSafeHwnd(),GWL_EXSTYLE)^0x80000| WS_EX_TRANSPARENT);
- HINSTANCE hInst=LoadLibrary(_T("User32.DLL"));
- if(hInst)
- {
- typedef BOOL (WINAPI * MYFUNC)(HWND,COLORREF,BYTE,DWORD);
- MYFUNC fun=NULL;
- //取得SetLayeredWindowAttributes函数指针
- fun=(MYFUNC)GetProcAddress(hInst,"SetLayeredWindowAttributes");
- if(fun)
- fun(m_pDlg->GetSafeHwnd(),0,128,2);
- FreeLibrary(hInst);
- }
- //移动该对话框窗口,置顶,覆盖找茬游戏界面中右边的那幅图。
- CRect rect;
- CWnd* pGameWnd = FindWindow(NULL, _T("大家来找茬"));
- pGameWnd->GetClientRect(&rect);
- pGameWnd->ClientToScreen(&rect);
- if(!m_pDlg->SetWindowPos(&wndTopMost, rect.left + m_ShowOffsetX, rect.top + m_ShowOffsetY,
- 497, 448, SWP_SHOWWINDOW))
- {
- MessageBox(_T("设置对话框失败!"), _T("温情提示"));
- return;
- }
- }
- else
- {
- MessageBox(_T("创建对话框对象失败!"), _T("温情提示"));
- return;
- }
- //显示不同
- CDC* pShow = m_pDlg->GetDC();
- ShowFault(pShow,pBuffer);
- //m_GameMain.SaveBMP(pBuffer, _T("D://result3.bmp") );
- if(pBuffer)
- {
- delete[] pBuffer;
- pBuffer = NULL;
- }
- }
上面的函数不仅显示了对话框,而且将位置通过SetWindowPos()调整到当前游戏窗口中图片的上方
然后就是把不同的地方显示出来
- // 显示不同的地方
- void CFindFaultDlg::ShowFault(CDC* pDC, DWORD* pBuffer)
- {
- CBitmap bm;
- bm.CreateBitmap(m_GameMain.GetPicFrame().x,m_GameMain.GetPicFrame().y,1,32, pBuffer);
- //bm.LoadBitmap(_T("I://result.bmp"));
- CBrush brush;
- brush.CreatePatternBrush(&bm);
- CBrush* pOldBrush = (CBrush*)pDC->SelectObject(&brush);
- pDC->FillRect(&CRect(0, 0, m_GameMain.GetPicFrame().x,m_GameMain.GetPicFrame().y),&brush);
- pDC->SelectObject(pOldBrush);
- brush.DeleteObject();
- bm.DeleteObject();
- ReleaseDC(pDC);
- }
这里是用刚才比较结束后的缓冲区内容的位图信息创建了一个画刷brush,然后用该画刷填充整个窗口,这样就结束了。
后记:通过制作这个小小的辅助工具,了解了获取句柄的方法,遇到了许多问题,比如32位位图和24位位图就有很大区别。
还有半透明窗口的显示。而且,最后遇到了一个很诡异的问题,这个程序到这里在我自己的电脑上都很正确,可是当我在我家的电脑上使用的时候,就总是在中间多出来一个方块的东西,而且只能显示一次,再点显示也是无效,无奈在家电脑也装上VS来调试,调试的时候也没发现什么问题,该执行的语句都执行了。如果有哪位高手知道这个问题的答案,还望多多指教。顺便说一句,我的系统是Win7,家中的是Vista。
为了解决这个问题,我最后采用了另外一种方法:不是通过截取窗口,而是截取屏幕,结果就正常了。。。
- // 通过截屏来获取图像
- bool CMain::GetPicByCap(DWORD*& lpvbits, int xSrc, int ySrc, int nWidth, int nHeight)
- {
- HWND hWnd = m_pGame; //得到句柄
- HDC hdcScreen;
- HDC hdcWindow;
- HDC hdcMemDC = NULL;
- HBITMAP hbmScreen = NULL;
- BITMAP bmpScreen;
- // Retrieve the handle to a display device context for the client
- // area of the window.
- hdcScreen = GetDC(NULL);
- hdcWindow = GetDC(hWnd);
- hdcMemDC = CreateCompatibleDC(hdcWindow);
- if(!hdcMemDC)
- {
- MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK);
- goto done;
- }
- // Get the client area for size calculation
- RECT rcClient;
- GetClientRect(hWnd, &rcClient);
- POINT pPos;
- pPos.x = xSrc;
- pPos.y = ySrc;
- ClientToScreen(hWnd, &pPos);//转换坐标
- //This is the best stretch mode
- //SetStretchBltMode(hdcWindow,HALFTONE);
- // Create a compatible bitmap from the Window DC
- hbmScreen = CreateCompatibleBitmap(hdcScreen, nWidth, nHeight);
- if(!hbmScreen)
- {
- MessageBox(hWnd, L"CreateCompatibleBitmap Failed",L"Failed", MB_OK);
- goto done;
- }
- // Select the compatible bitmap into the compatible memory DC.
- SelectObject(hdcMemDC,hbmScreen);
- // Bit block transfer into our compatible memory DC.
- if(!BitBlt(hdcMemDC,
- 0,0,
- nWidth, nHeight,
- hdcScreen,
- pPos.x, pPos.y,
- SRCCOPY))
- {
- MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
- goto done;
- }
- // Get the BITMAP from the HBITMAP
- GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);
- BITMAPFILEHEADER bmfHeader;
- BITMAPINFOHEADER bi;
- bi.biSize = sizeof(BITMAPINFOHEADER);
- bi.biWidth = bmpScreen.bmWidth;
- bi.biHeight = bmpScreen.bmHeight;
- bi.biPlanes = 1;
- bi.biBitCount = 32;
- bi.biCompression = BI_RGB;
- bi.biSizeImage = 0;
- bi.biXPelsPerMeter = 0;
- bi.biYPelsPerMeter = 0;
- bi.biClrUsed = 0;
- bi.biClrImportant = 0;
- DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
- if(lpvbits)
- {
- delete[] lpvbits;
- lpvbits = NULL;
- }
- lpvbits = new DWORD[nWidth*nHeight];
- // Gets the "bits" from the bitmap and copies them into a buffer
- // which is pointed to by lpbitmap.
- GetDIBits(hdcWindow, hbmScreen, 0,
- (UINT)bmpScreen.bmHeight,
- lpvbits,
- (BITMAPINFO *)&bi, DIB_RGB_COLORS);
- //Clean up
- done:
- DeleteObject(hbmScreen);
- ReleaseDC(hWnd, hdcMemDC);
- ReleaseDC(NULL,hdcScreen);
- ReleaseDC(hWnd,hdcWindow);
- return true;
- }
这个是从MSDN上参考而来的。其实很简单,就是用ClientToScreen(hWnd, &pPos);把窗口中的坐标转换为对应的屏幕坐标,其他的和第一个是一样的。
附:
各图片位置,窗口大小等信息,这个找起来很麻烦。。。
m_nPicWidth = 498-1; //左右两副图本身有偏移1个像素,去掉偏移的,只比较共有的部分
m_nPicHeight = 448;
m_nOffsetLeftPicX = 8;
m_nOffsetLeftPicY = 193;
m_nOffsetRightPicX = 516 + 1; //左右两副图本身有偏移1个像素,去掉偏移的,只比较共有的部分
m_nOffsetRightPicY = 193;