突然想玩一下键盘弹曲子,就找到了freepiano,专业的东西不懂,就找了写简谱来玩玩,感觉挺不错的,哈哈~~
玩疯了之后,突然想到,我平时写代码,是不是可以弹出一段曲子呢,是不是心情会变得非常好,代码也写的更有节奏呢~~
说不定还搞出来一个什么《代码之歌》的钢琴曲~~ 嘎嘎
突然被自己这个想法吸引住了,不管咋样,每敲下代码的一个字符,后面想起了背景音乐,真是不错的,程序员也可以是“钢琴师”啊~~
有了想法,就开整!!!
有下面几点问题:
忘了说,freepiano长这样:
开搞
先简单整理下思路:
首先想到的就是DLL劫持和修改freepiano的导入表,后者不够优雅,果断要选择dll劫持。
然后就用depends看了下freepiano的导入信息,发现几个可以劫持的(dsound.dll,d3d9.dll等),简单代码确认了一下,freepiano可以劫持这两个模块,选择了d3d9.dll(函数少)。
然后偷懒用了aheadlib导出了d3d9.dll的导出函数信息,简单方便,飞快得就搞定了劫持。
代码很简单,就贴一点(都不需要手写):
// 导出函数 #pragma comment(linker, "/EXPORT:Direct3DShaderValidatorCreate9=_AheadLib_Direct3DShaderValidatorCreate9,@1") #pragma comment(linker, "/EXPORT:PSGPError=_AheadLib_PSGPError,@2") #pragma comment(linker, "/EXPORT:PSGPSampleTexture=_AheadLib_PSGPSampleTexture,@3")... // 导出函数 ALCDECL AheadLib_Direct3DShaderValidatorCreate9(void){ // 保存返回地址 __asm POP m_dwReturn[0 * TYPE long]; // 调用原始函数 GetAddress("Direct3DShaderValidatorCreate9")(); // 转跳到返回地址 __asm JMP m_dwReturn[0 * TYPE long]; }
一试,OK,模块起来了,freepiano正常工作。
2. 安装钩子
选择了安装全局WH_KEYBOARD钩子,这个代码网上也太多了,就不细说了,看看就行
//安装钩子BOOL Hook(HMODULE hMod){ g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hMod, 0); return g_Hook?TRUE:FALSE; } //卸载钩子VOID Unhook(){ if(g_Hook) { UnhookWindowsHookEx(g_Hook); } }//钩子函数,劫持键盘消息LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ) { if(code == HC_ACTION) { SendKeyMsg(wParam, lParam); } return CallNextHookEx(g_Hook, code, wParam, lParam); }
首先想到的就是在钩子函数里给freepiano发送WM_KEYDOWN/WM_KEYUP消息就行了。
先找到freepiano的窗口,spy++上,找到窗口标题和类型信息,然后代码:
HWND hwnd = FindWindow("FreePianoMainWindow", "Wispow Freepiano 2"); if(hwnd == NULL) { hwnd = FindWindow("FreePianoMainWindow", NULL); if(hwnd == NULL) { hwnd = FindWindow(NULL, "Wispow Freepiano 2"); } }
然后就是发消息:
if(hwnd) { SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); if(keydown) { keydown = false; PostMessage(hwnd, WM_KEYDOWN, wParam, lParam); }else { keydown = true; PostMessage(hwnd, WM_KEYUP, wParam, lParam); } }
测试,失败了,没有预想的效果。
分析原因:
v7.lpfnWndProc = (WNDPROC)xxx_main_wndproc_41D070; //窗口响应函数 v7.cbClsExtra = 0; v7.cbWndExtra = 0; v7.hInstance = GetModuleHandleA(0); v7.hIcon = LoadIconA(v1, (LPCSTR)0xA); v7.hCursor = 0; v7.hbrBackground = 0; v7.lpszMenuName = 0; v7.lpszClassName = "FreePianoMainWindow";
然后发现居然没有对WM_KEYDOWN/WM_KEYUP/WM_CHAR之类的消息进行处理,那是怎么接受的按键信息
进钩子函数一下,各种按键状态记录的处理,不深究了。基本确认他使用这种方式来接受按键信息。
v6 = (unsigned __int8)byte_4F6DC8[scanCode]; if ( (unsigned int)(v6 - 1) > 0x6A ) goto LABEL_23; if ( (unsigned __int8)byte_4F6ED0[v6] != pressed_0 ) { byte_4F6ED0[v6] = pressed_0; sub_449B20(v6, pressed_0 != 0); } if ( (_BYTE)dword_4F6DC0 || BYTE1(dword_4F6DC0) && (v6 == 'D' || v6 == 'H') || BYTE2(dword_4F6DC0) && (byte_4F6F15 || byte_4F6F17) && v6 == 28 ) result = 1; else
那就不能直接PostMessage发送消息了。
g_Hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hMod, 0);
LRESULT CALLBACK LowLevelKeyboardProc( int nCode, // hook code WPARAM wParam, // message identifier LPARAM lParam // message data ){ COPYDATASTRUCT CopyData = {0}; KeyboardLL_Msg Msg = {0}; Msg.nCode = nCode; Msg.wParam = wParam; memcpy(&Msg.lParam, (char*)lParam, sizeof(KBDLLHOOKSTRUCT)); CopyData.cbData = sizeof(KeyboardLL_Msg); CopyData.dwData = 0; CopyData.lpData = &Msg; HWND hwnd = FindFreepiano(); if(hwnd) { BOOL ret = SendMessage(hwnd, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&CopyData); } return CallNextHookEx(g_Hook, nCode, wParam, lParam); } typedef struct _KeyboardLL_Msg { int nCode; WPARAM wParam; KBDLLHOOKSTRUCT lParam; }KeyboardLL_Msg, *PKeyboardLL_Msg;
通过一个线程,循环查找freepianp窗口(可能还没起来),然后hook窗口响应函数
void HookWinProc() { while(1) { HWND hwnd = FindFreepiano(); if(hwnd) { g_WndProc = (pfn_WindProc)GetWindowLong(hwnd, GWL_WNDPROC); if(g_WndProc) { SetWindowLong(hwnd, GWL_WNDPROC, (LONG)fakeWindowProc); break; } } Sleep(10); } }
自己的函数中加入对WM_COPYDATA的消息处理,调用freepiano的钩子函数g_LowLevelKeyboardProc发键盘消息过去。
LRESULT WINAPI fakeWindowProc( HWND hWnd, // handle to window UINT Msg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { if(Msg == WM_COPYDATA) { COPYDATASTRUCT* CopyData = (COPYDATASTRUCT*)lParam; //if(CopyData->cbData == sizeof(KeyboardLL_Msg)) { KeyboardLL_Msg* Msg = (KeyboardLL_Msg*)CopyData->lpData; g_LowLevelKeyboardProc(Msg->nCode, Msg->wParam, (LPARAM)&Msg->lParam); } } return g_WndProc(hWnd, Msg, wParam, lParam); }
g_LowLevelKeyboardProc地址这里使用硬编码,图方便
HMODULE hExe = GetModuleHandle(NULL);g_LowLevelKeyboardProc = (pfn_LowLevelKeyboardProc)((DWORD)hExe + (DWORD)g_LowLevelKeyboardProc);
功能到这里基本搞定。
测试通过。
手指立马不受控制的在编辑器里、浏览器、文件浏览器里各种按键,然后耳边响起了悠扬(忽略乱打的节奏的话)的钢琴声~~
然后录了一段程序员启蒙歌-《hello world》!你们感受一下:
在线试听:https://pan.baidu.com/s/1cIlPOM
#include <stdio.h> int main(){ printf("hello world!"); return 0; }
由于手抽,打错了几个字,所以可以听到多次短暂的退格琴声,表介意~~~
可能的优化:
有兴趣的同学可以去折腾,我这里就不继续了~~
源码+工具:https://github.com/anhkgg/coding_piano
已经编译了,直接下载就可以试用,src目录是源码
有兴趣的可以去看看我得博客:http://anhkgg.github.io/coding-piano-hook/