朱皮特的博客 自由的飞翔

QQ表情菜单的逆向及实现

2009-02-12
朱皮特
 
阅读量:

逆向探索–表情菜单解密

创建一个基于对话框的MFC应用程序QQFaceMenu(对话框为QQFaceMenuDlg),在文件QQFaceMenuDlg.h中的DECLARE_MESSAGE_MAP() 之前添加声明:

afx_msg void OnMeasureItem(int nIDCtl,LPMEASUREITEMSTRUCT lpmis);
afx_msg void OnDrawItem(int nIDCtl,LPDRAWITEMSTRUCT lpdis);
afx_msg void OnFaceMenuClicked(UINT nID);

在QQFaceMenuDlg.cpp文件中完善上述处理程序:

void CQQFaceMenuDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis) {
	if (lpmis->CtlType == ODT_MENU) {
		lpmis->itemHeight = 20 + 2;
		lpmis->itemWidth = 10;
	}
}

void CQQFaceMenuDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis) {
	int index;
	CDC dc;
	POINT pt;


	CBrush *pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU));
	dc.Attach(lpdis->hDC);
	dc.FrameRect(&(lpdis->rcItem), pBrush);
	delete pBrush;

	if (lpdis->CtlType == ODT_MENU) {

		index = lpdis->itemID - 0x785;
		pt.x = lpdis->rcItem.left + 1;
		pt.y = lpdis->rcItem.top + 1;
		ImgQQFace.Draw(&dc, index, pt, ILD_NORMAL);
	}
	dc.Detach();
}

void CQQFaceMenuDlg::OnFaceMenuClicked(UINT nID) {
	CClientDC dc(this);
	POINT pt;

	pt.x = 0;
	pt.y = 0;
	ImgQQFace.Draw(&dc, nID - 0x785, pt, ILD_NORMAL);
}

在BEGIN_MESSAGE_MAP(CQQFaceMenuDlg, CDialog)和END_MESSAGE_MAP()之间添加消息映射表:

ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
ON_COMMAND_RANGE(0x785,0x7B6,OnFaceMenuClicked)

在CQQFaceMenuDlg::OnInitDialog()中初始化ImgQQFace:

// TODO: Add extra initialization here
CGdiObject GdiObj;
ImgQQFace.Create(20,20,25,50,1);
GdiObj.Attach((HIMAGELIST)LoadBitmap(GetModuleHandle(NULL),MAKEINTRESOURCE(IDB_QQFace)));
ImageList_AddMasked(ImgQQFace.m_hImageList,(HBITMAP)GdiObj.m_hObject,0X808000);//CYAN
GdiObj.DeleteObject();

OnBtnShowFaceMenu是单击按钮时的处理过程,它负责将表情菜单弹出来:

void CQQFaceMenuDlg::OnBtnShowFaceMenu() {
	// TODO: Add your control notification handler code here
	CMenu   popFaceMenu;
	POINT   point;

	popFaceMenu.Attach(CreatePopupMenu());

	int flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
	for (int col = 0; col < 10; col++) {
		int index;

		for (int row = 0; row < 5; row++) {
			index = row * 10 + col;
			AppendMenu(popFaceMenu.m_hMenu, flags, index + 0x785, NULL);
			flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
		}
		flags |= MF_MENUBREAK;
	}

	GetCursorPos(&point);

	popFaceMenu.TrackPopupMenu(0x62, point.x, point.y, AfxGetApp()->GetMainWnd());
}

QQ表情菜单实现一

腾讯QQ或者QQGame客户端几乎都提供了一个表情菜单:

感觉很有意思,动手实现。整个资源图片可以在相关程序的安装文件中找到:

不过实现思想还不太会,于是就逆向了一下“大家来找茬”的客户端程序,看看人家是怎么

实现的。完了之后就写实现代码了:

用VC新建一个基于Dialog的项目,在OnInitDialog()事件中加载图像资源:

CGdiObject GdiObj;
ImgQQFace.Create(20,20,25,50,1);
GdiObj.Attach((HIMAGELIST)LoadBitmap(GetModuleHandle(NULL),MAKEINTRESOURCE(IDB_QQFace)));
ImageList_AddMasked(ImgQQFace.m_hImageList,(HBITMAP)GdiObj.m_hObject,0X808000);//CYAN
GdiObj.DeleteObject();

其中ImgQQFace是CImageList 类型的,点击一个按钮弹出这个表情菜单,则事件代码应是:

void CQQFaceMenuDlg::OnBtnShowFaceMenu() {
	// TODO: Add your control notification handler code here
	CMenu   popFaceMenu;
	POINT   point;

	popFaceMenu.Attach(CreatePopupMenu());

	int flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
	for (int col = 0; col < 10; col++) {
		int index;

		for (int row = 0; row < 5; row++) {
			index = row * 10 + col;
			AppendMenu(popFaceMenu.m_hMenu, flags, index + 0x785, NULL);
			flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
		}
		flags |= MF_MENUBREAK;
	}

	GetCursorPos(&point);

	popFaceMenu.TrackPopupMenu(0x62, point.x, point.y, AfxGetApp()->GetMainWnd());
}

添加的菜单属于自制菜单,应该相应WM_MEASUREITEM和WM_DRAWITEM消息来画菜单:

void CQQFaceMenuDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis) {
	if (lpmis->CtlType == ODT_MENU) {
		lpmis->itemHeight = 20 + 2;
		lpmis->itemWidth = 10;
	}
}

void CQQFaceMenuDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis) {
	int index;
	CDC dc;
	POINT pt;


	CBrush *pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU));
	dc.Attach(lpdis->hDC);
	dc.FrameRect(&(lpdis->rcItem), pBrush);
	delete pBrush;

	if (lpdis->CtlType == ODT_MENU) {

		index = lpdis->itemID - 0x785;
		pt.x = lpdis->rcItem.left + 1;
		pt.y = lpdis->rcItem.top + 1;
		ImgQQFace.Draw(&dc, index, pt, ILD_NORMAL);
	}
	dc.Detach();
}

完成。

QQ表情菜单实现二

腾讯QQ或者QQGame客户端几乎都提供了一个“表情菜单”,在较早的版本里实现原理确实是靠菜单来完成的,详见上节。

不过到了2009版本以后,“表情菜单”却是靠窗口绘图来实现的,下面是我模拟的效果:

鼠标移动时gif预览是动态显示的(静态截图体现不出来),程序按[Esc]键可以退出。


Comments

Content