-
06-10-2007, 05:24 PM #1Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
Hook – Ví dụ nhỏ dành cho "người mù"
Hook là gì?
Trong Windows, khi chúng ta thực hiện các thao tác nhấp chuột, nhấn phím… thì hệ điều hành sẽ chuyển các sự kiện này thành các thông điệp (message) rồi đưa vào hàng đợi (queue) của hệ thống. Sau đó, các thông điệp được trao lại cho từng ứng dụng cụ thể để xử lý.
Hook là một kỹ thuật cho phép một hàm có thể chặn, theo dõi, xử lý, hoặc hủy bỏ các thông điệp trước khi chúng “mò” đến được ứng dụng.
Hai ví dụ thường gặp của Hook là ứng dụng soạn thảo văn bản tiếng Việt (Unikey, Vietkey…) và ứng dụng tra từ điển trực tiếp trên màn hình (Click’n’See, Lạc Việt MTD, English Study…). Chúng xử lý thông điệp từ bàn phím để đổi văn bản sang tiếng Việt, hoặc xử lý thông điệp từ con chuột để lấy văn vản dưới con trỏ. Chương trình KeyLogger chuyên ăn cắp mật khẩu cũng sử dụng kỹ thuật này.
Hook hoạt động như thế nào?
Xét ở mặt phạm vi hoạt động thì có 2 loại Hook: Hook toàn cục (có phạm vi ảnh hưởng đến toàn hệ thống) và Hook cục bộ (chỉ có tác dụng trên ứng dụng được cài Hook).
Xét về mặt chức năng, Hook có 15 loại ứng với nhóm sự kiện mà nó sẽ xử lý. Ví dụ:
- WH_KEYBOARD: cho phép đón nhận các thông điệp từ bàn phím
- WH_MOUSE cho phép đón nhận các sự kiện có liên quan đến con trỏ chuột
- WH_MSGFILTER và WH_SYSMSGFILTER: cho phép theo dõi chính các thông điệp được xử lý bởi menu, scrollbar, dialog…
Các loại còn lại bạn có thể tìm thấy trong CD MSDN tại /winui/winui/windowsuserinterface/windowing/hooks/abouthooks.htm
Ứng với mỗi loại Hook, Windows sẽ có một chuỗi các hàm lọc (filter function) để xử lý. Ví dụ, khi người dùng nhấn phím, thông điệp này sẽ được truyền qua tất cả các hàm lọc thuộc nhóm WH_KEYBOARD.
Các hàm lọc này sẽ do chúng ta tùy ý định nghĩa. Mục tiêu của hàm lọc và bắt lấy thông điệp để giám sát, sửa đổi và chuyển tiếp, hoặc ngăn chặn không cho nó đến được ứng dụng. Hàm lọc được gọi bởi Windows nên còn gọi là hàm CallBack.
Muốn cài đặt một Hook toàn cục (Hook hệ thống), thì hàm lọc phải được đặt trong file DLL.
Cài đặt Hook có khó không?
Về cơ bản, chỉ cần biết chút kiến thức về hệ điều hành Windows (cơ chế hoạt động và quản lý thông điệp). Thêm một ít kiến thức về C++, API, và DLL (Dynamic Link Library). Tuy nhiên, Hook là một… nghệ thuật (và người Hook cũng là 1 nghệ sỹ [IMG]images/smilies/biggrin.png[/IMG]), và chúng ta có thể làm đủ trò tùy vào trí tưởng tượng và “nội công” của mình.
Sơ lượt các bước cài đặt (một Hook hệ thống)
1. Bước 1: tạo file DLL chứa hàm lọc. Mọi hàm lọc đều có dạng như sau:
Mã:LRESULT CALLBACK <tên hàm>(int nCode, WPARAM wParam, LPARAM lParam)
- Nếu nCode < 0 thì không được làm gì mà phải gọi ngay hàm CallNextHookEx() (sẽ trình bày sau) và trả về giá trị... return bởi nó.
- Nếu có xử lý ít nhiều thì hàm lọc phải trả về giá trị khác 0
2. Bước 2: tạo chương trình cài đặt Hook. Chương trình này sử dụng 3 hàm API sau đây để “móc” hàm lọc vào các sự kiện:
a) Hàm cài đặt một hàm lọc
Mã:HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId)
+ idHook: xác định loại hook mà ta muốn cài đặt (ví dụ: WH_KEYBOARD, WH_MSGFILTER…)
+ lpfn : địa chỉ hàm lọc mà ta muốn gắn với hook
+ hMod : handle của module chứa hàm lọc (là handle của file DLL trong trường hợp Hook toàn cục)
+ dwThreadID : định danh của thread ứng với hook đang được cài đặt (nếu = 0 thì Hook có phạm vi toàn hệ thống và bao hảm cả mọi thread đang tồn tại)
b) Hàm gỡ bỏ một hàm lọc
Mã:BOOL UnhookWindowsHookEx(HHOOK hHook)
+ hHook: handle của hook cần gỡ bỏ (chính là giá trị được trả vể bởi hàm SetWindowsHookEx khi cài đặt)
c) Hàm gọi hàm lọc kế tiếp trong chuỗi
Như đã nói ở trên, hệ thống duy trì một chuỗi các hàm lọc cho mỗi loại sự kiện. Do đó, sau khi một hàm lọc thực hiện xong “nghĩ vụ” của mình, nó sẽ gọi hàm lọc tiếp theo trong chuỗi. Nhờ đó, người ta có thể cài nhiều Hook tại một sự kiện.
Mã:LRESULT CallNextHookEx(HHOOK hHook, int nCode, WPARAM wParam, LPARAM lParam)
+ hHook: là handle của hook hiện hành
+ nCode : hook code để gởi đến hook kế tiếp
+ wParam và lParam: chứa thông tin mở rộng của thông điệp
Nhứt đầu quá! Ví dụ đi
Ví dụ dưới đây (có file đính kèm) sẽ minh họa cách cài một cái Hook đơn giản bằng Visual C++: chặn bắt sự kiện nhấn phím. Khi chạy, chương trình sẽ “tàng hình” (chỉ nhìn thấy trong Task Manager). Nếu người dùng “lỡ tay” nhấn phím Enter (ở bất kỳ đâu) sẽ bị “bắn” ra một cái message box có dòng chữ “sonhn say hello” (rất dễ nổi khùng!).
1. Bước 1:
Trước hết, tạo một Project VC++ Win32 DLL (xin đừng nhầm với dạng DLL Class Library) với 1 file duy nhất có nội dung như sau:
File “TestDLL.cpp”
Mã:#include <windows.h> //vùng nhớ dùng chung, chứa biến handle của Hook#pragma data_seg("SHARED_DATA")HHOOK hGlobalHook = NULL;#pragma data_seg() //hàm lọc sự kiện nhấn phím__declspec(dllexport) LRESULT CALLBACK FillKeyboard(int nCode, WPARAM wParam, LPARAM lParam) { /ếu sự kiện là nhấn phím và mã phím là Enter if ((nCode == HC_ACTION) && (wParam == 13)) { MessageBox(0, "sonhn say hello :)", "Hello", 0); return 1; } //gọi Filter Function kế tiếp trong chuỗi các Filter Function return CallNextHookEx(hGlobalHook, nCode, wParam, lParam);} //hàm ấn định biến hGlobalHook tại vùng nhớ dùng chung__declspec(dllexport) void SetGlobalHookHandle(HHOOK hHook){ hGlobalHook = hHook;}
- Đoạn khai báo vùng nhớ dùng chung “SHARED_DATA”: vùng nhớ này chứa handle của Hook được cài. Sở dĩ nó phải được đặt trong vùng nhớ dùng chung vì cả 2 chương trình (DLL và EXE) đều cần truy phải truy xuất vào.
- Hàm lọc sự kiện nhấn phím: hàm này thực hiện nhiệm vụ: khi sự kiện là nhấn phím (nCode == HC_ACTION) và phím được nhấn là Enter (wParam == 13) thì “bắn” cái message box ra.
- Hàm ấn định biến hGlobalHook: hàm này sẽ được gọi bởi chương trình EXE sau đây để ấn định handle cho Hook mà nó sẽ cài.
Tạo file Module-Definition (.def) có nội dung như bên dưới để export các hàm bên trong file DLL và khai báo vùng nhớ dùng chung.
File “TestDLL.def”
Mã:LIBRARY "TestDLL"EXPORTS FillKeyboard SetGlobalHookHandle SECTIONS SHARED_DATA Read Write Shared
2. Bước 2:
Tạo project VC++ dạng Win32 Application để thực hiện cài hàm lọc (trong file TestDLL.dll) vào sự kiện nhấn phím.
File “TestHook.cpp”
Mã:#include <windows.h> //các biến toàn cụcHHOOK hHook = NULL; //handle của hookHMODULE hDll; //handle của DLL //định nghĩa con trỏ hàm SetGlobalHookHandle() trong file DLLtypedef VOID (*LOADPROC)(HHOOK hHook); BOOL InstallHook(); int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { //cài đặt hook, exit nếu thất bại if (InstallHook() == FALSE) { MessageBox(0, "Can not install hook!", "Error", 0); return -1; } MSG msg; BOOL bRet; //vòng lặp đón và xử lý các message while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0;} //Hàm cài đặt hookBOOL InstallHook() { //load file DLL if (LoadLibrary("TestDLL.dll") == NULL) { MessageBox(0, "Can not load DLL file.", "Error", 0); return FALSE; } //lấy handle của file DLL HMODULE hDLL = GetModuleHandle("TestDLL"); //exit nếu load DLL không thành công if (hDLL == NULL) { return FALSE; } //cài đặt hook, phạm vi toàn cục hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hDLL,"FillKeyboard"), hDLL, 0); //exit nếu cài đặt Hook không thành công if (hHook == NULL) { return FALSE; } //lấy địa chỉ hàm SetGlobalHookHandle() trong file DLL LOADPROC fPtrFcnt; fPtrFcnt = (LOADPROC)GetProcAddress(hDLL, "SetGlobalHookHandle"); if (fPtrFcnt == NULL) { return FALSE; } //ấn định handle của hook vào vùng nhớ dùng chung (giữa DLL và ứng dụng này) fPtrFcnt(hHook); return TRUE;}
Chú ý:
- Nhằm đơn giản hóa quá trình cài đặt, ví dụ này không có thao tác gỡ bỏ Hook. Khi chạy, chương trình sẽ “tàng hình” và muốn tắt nó, bạn phải “end task” nó bằng Task Manager(!). Lúc đó, Hook cũng sẽ được tự động được gỡ bỏ.
- Nếu bạn muốn cài đặt thao tác gỡ bỏ Hook, hãy viết thêm đoạn code xử lý sự kiện WM_DESTROY và gọi hàm dưới đây:
Mã://hàm gỡ bỏ hook, với hHook là handle của hookBOOL UninstallHook() { if ((hHook != 0) && (UnhookWindowsHookEx(hHook) == TRUE)) { hHook = NULL; return TRUE; } return FALSE;}
Hook là một kỹ thuật tương đối khó và kiến thức về nó cũng khá rộng, khó có thể trình bày hết trong phạm vi một bài viết. Các bạn quan tâm có thể tìm hiểu thêm trên website http://msdn.microsoft.com và các tài liệu liên quan. Chúc thành công.View more random threads:
- Lập trình đồ họa | Từng bước xây dựng cho mình một thư viện đồ họa - GDrawing
- Ở Đâu Sửa Laptop Tại Chỗ Q 5 Chất Lượng
- Screen Reader Visual C++ | Đọc nội dung trên màn hình viết bằng VC++
- Dẫn nhập vào lập trình Win32 api
- Kỹ thuật Sprite Xóa Ảnh Thừa Trong Game
- Một số kĩ thuật cơ bản với Irrlicht
- Thư Viện Hỗ trợ Hook API
- Memory tracker
- Hotgirl Trâm Anh lộ clip nóng mới nhất
- Tìm hiểu về từ khóa const - const với nhiều ý nghĩa
-
10-12-2010, 12:33 PM #2Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
Về phần hook mình không làm được phần hook hàm của win, đại loại kiểu hook để làm click&see ấy, bạn nào có kinh nghiệm có thể hướng dẫn mình phần này được không
-
25-02-2011, 10:08 AM #3Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
Xin tổng hợp thêm tài liệu sưu tầm ở đây
Tài liệu sưu tầm trên blog của Benina</font>
001 : _http://rootbiez.blogspot.com/2009/11/programming-co-che-hook-va-ung-dung-to.html
[<font color="Red">Programming] Cơ chế Hook và ứng dụng to lớn của nó
Cơ chế Hook và ứng dụng to lớn của nó
Author: griever
Link: _http://cnpmk51-bkhn.org/forums/archive/index.php?t-622.html
Bài 1:
hnay đang kiếm guide để làm autoPlay cho vltk thì đc biết đến cái này, ngồi 1 lúc ngẫm ra mấy thứ lên đây chia sẻ cùng a e.
chắc mọi người đã nghe qua nhiều về nó, ví dụ làm Vietkey hay Unikey đều phải sử dụng cơ chế này
ngoài ra cơ chế này còn sử dụng để làm keylogger, autoPlay của các game oline...
đầu tiên, em xin nói qua về cơ chế thông điệp của Windows
- khi 1 sự kiện xảy ra tại cửa sổ con (click chuột, gõ 1 phím...), HDH sẽ phát ra 1 thông điệp tới cửa sổ cha, ở đây thông điệp sẽ đc hàm xử lý của cửa sổ cha xử lý. Tùy theo thông điệp đc gửi đến là gì và mục đích của người lập trình mà cách thức xử lý sẽ khác nhau.
- hàm xử lý là hàm dùng để xử lý các thông điệp đã đc chặn, bắt
- có thể tác động đến 1 cửa sổ bất kỳ bằng cách gửi thông điệp đến chính cửa sổ đó. Khi đó nó sẽ tiếp nhận thông điệp này và thực hiện các yêu cầu của thông điệp.
khi học các hàm API mọi người sẽ nghĩ ngay đến hàm SendMessage( )
- 1 cửa sổ có thể là 1 button, 1 edit text, 1 hộp thoại,...
tiếp theo, em xin nói về bản chất của Hook (đây là theo "ngu ý" của em thôi): Hook thực chất là bẫy & sửa các thông điệp của 1 cửa sổ bất kỳ trước khi các thông điệp này đc hàm xử lý của cửa sổ đó xử lý.
nói cách khác, khi HDH gửi 1 thông điệp đến cửa sổ cha, Hook sẽ chặn các thông điệp này, thay đổi chúng...tùy theo mục đích của người lập trình.
ví dụ khi 1 ứng dụng đc khởi động (ví dụ Yahoo chẳng hạn), thông điệp WM_CREATE sẽ đc phát ra, khi đó nếu ta dùng Hook để bắt lấy thông điệp này và thay nó bằng thông điệp WM_DESTROY thì ứng dụng đó hiển nhiên sẽ tự động exit! (giống kavo đó)
vậy tại sao dùng cơ chế chặn bắt thông thường kô đc???:Smlie (105):
sẽ có ý kiến cho rằng có thể dùng hàm SendMessage( ) để gửi thông điệp WM_DESTROY tới 1 ứng dụng nào đó để làm cho ứng dụng đó tự exit! Hoặc gửi thông điệp WM_GETTEXT tới 1 edit text để lấy đc nội dung của edit text đó! Như vậy thì làm keylogger hay virus sẽ đơn giản hơn bao nhiêu [IMG]images/smilies/biggrin.png[/IMG]
nếu ai đã biết về hàm này thì sẽ biết là hàm này yêu cầu HANDLE của cửa sổ tiếp nhận thông điệp, hiểu nôm na thế này: thông điệp là 1 gói bưu kiện, HDH là ngưởi gửi bưu kiện, cửa sổ đang đc nói đến là người nhận bưu kiện, HANDLE của cửa sổ là dịa chỉ của ngươi nhận, còn hàm SendMessage( ) là công ty chuyển phát.
tuy nhiên có những lúc ta kô chỉ cần hàm sendmessage này, bởi hàm này chỉ có tác dụng gửi thông điệp đi chứ kô có tác dụng "nghe" thông điệp được gửi đến cho chương trình.
do đó, sử dụng cơ chế chặn bắt thông thường thì hầu như là vô tác dụng! => phải dùng Hook thôi [IMG]images/smilies/smile.png[/IMG]
cách cài đặt Hook thì em kô nói ở đây vì kiếm trên google chắc không thiếu [IMG]images/smilies/biggrin.png[/IMG]
bài sau em xin nói về 1 ứng dụng quan trọng khác của Hook đó là "lây" code (inject code) vào 1 ứng dụng bên ngoài
Bài 2:
h em xin trình bày 1 ứng dụng quan trọng khác của Hook là làm keylogger hay malware!
phần này em nêu ra chỉ với mục đích "cọ xát" và "học hỏi" là chính, vì bản thân em cũng mới tìm hiểu về lĩnh vực này thôi, và cũg chưa hiểu tường tận lắm [IMG]images/smilies/biggrin.png[/IMG]
Keylogger chắc ai cũg biết [IMG]images/smilies/biggrin.png[/IMG], đó là phần mêm dùng để thu thập thông tin vê password hay ID bằng cách bắt các thông điệp về bàn phím. ở đây em xin nói đến 1 loại keylogger khác, mục đích cũng giống như trên nhưng cách làm "nông dân" hơn. đó là lấy thông tin của edit text chứa pass hay ID.
cách làm của Keylogger này khá là đơn giản, giống cách làm các loại autoplay bây h.
trước hết cần phải quy ước 1 số thuật ngữ
remote process: là ứng dụng chứa thông tin về pass và ID. Hoặc với các loại autoplay thì nó chính là các loại game oline(vltk...)
cách thức chung của các loại này là chèn ("lây") code vào remote process. Gọi là "lây" bởi vì đoạn code đó thực sự kô nằm trong remote process. chỉ là remote process sẽ thực hiện các đoạn code đã bị "lây nhiễm". Có 3 cách để lây nhưng em xin chỉ nói 1 cách dùng Hook thôi, đó là:
+ đặt code cân thiết vào 1 file DLL, nghĩa là khi viết 1 hàm thay vì ta viết hàm đó vào chương trinh chính thì ta viết trong DLL.
+ dùng Hook để map file DLL của ta vào remote process.
Sau khi đã thực hiện 2 bước trên thì remote process sẽ tự thực hiện các lệnh đã có trong đoạn code của ta
--2 bước trên thực hiện đêu rất dễ--cách viết DLL thì search trên mạng kô thiếu và thực hiện cũng khá dễ
đến đây thì có 2 hướng là keylogger và autoplay.
trước tiên nói về keylogger:
mục đích của keylogger là thu thập pass và ID, vậy trong file DLL ta chỉ cần xài hàm SendMessage là đủ. Khi đó ta tạo ra 1 hàm đệm trong DLL có các nhiệm vụ sau: cài Hook vào remote process, gọi hàm SendMessage với thông điệp WM_GETTEXT để lấy thông tin về pass và ID , bỏ cài đặt Hook (bước này giống như khi ta giải phóng 1 bộ nhớ đã đc cấp phát)
sau đây là cách thức thực hiện chi tiết:
B1: tạo 1 DLL có hàm đệm như trên
B2: tạo chương trinh chính có các chức năng như sau:
+ lấy handle của edit text chứa pass hay ID
+ link chương trinh chính này với DLL ở trên (khi link 1 DLL với 1 ứng dụng thi ứng dụng sẽ sử dụng đc các hàm trong DLL--đó là lí do tại sao nó đc gọi la DLL - thư viện liên kết động)
+ gọi hàm đệm trong DLL ra. Đến đây thì ta đã lấy đc pass và ID rồi [IMG]images/smilies/biggrin.png[/IMG], chỉ việc gửi pass này về mail của mình là xong [IMG]images/smilies/biggrin.png[/IMG]
B1 thì kô nói làm j nữa
B2 có các chú ý sau:
- làm sao lây đc handle của edit text chứa pass hay ID đây???
trong sample em down trên mạng vê thì nó dùng cơ chế bắt chuột. Khi con chuột rê đến đâu nó sẽ dùng 1 số hàm API để định vị cửa sổ mà con chuột đang chỉ đến, vì muốn gõ pass thì các pác phải click chuột vào cái khung password, khi đó con chuột chỉ đúng edit text chứa password [IMG]images/smilies/biggrin.png[/IMG] tuy nhiên theo kinh nghiệm của em thi cái đó rất hiếm xảy ra. Khi gõ ID xong thì các pác gõ pass thế nào, dùng chuột click vào khung password hay ấn tab? Em nghĩ là 80% là ấn Tab [IMG]images/smilies/biggrin.png[/IMG] => làm keylogger theo cách này không ổn lắm, em cũng chỉ đưa ra loại này để làm quen hơn với Hook thôi. Pác nào quan tâm làm thử cũng hay [IMG]images/smilies/biggrin.png[/IMG]
dưới đây là đoạn code dùng để định vị cửa sổ:
Mã:HWND SmallestWindowFromPoint( const POINT point ){ RECT rect, rcTemp; HWND hParent, hWnd, hTemp; hWnd = ::WindowFromPoint( point ); if( hWnd != NULL ) { ::GetWindowRect( hWnd, &rect ); hParent = ::GetParent( hWnd ); // Has window a parent? if( hParent != NULL ) { // Search down the Z-Order hTemp = hWnd; do{ hTemp = ::GetWindow( hTemp, GW_HWNDNEXT ); // Search window contains the point, hase the same parent, and is visible? ::GetWindowRect( hTemp, &rcTemp ); if(::PtInRect(&rcTemp, point) && ::GetParent(hTemp) == hParent && ::IsWindowVisible(hTemp)) { // Is it smaller? if(((rcTemp.right - rcTemp.left) * (rcTemp.bottom - rcTemp.top)) < ((rect.right - rect.left) * (rect.bottom - rect.top))) { // Found new smaller window! hWnd = hTemp; ::GetWindowRect(hWnd, &rect); } } }while( hTemp != NULL ); } } return hWnd;}
à, ở bài trước em có nhầm 1 chút: hàm SendMessage có thể gửi thông điệp đi bất kỳ đâu, kô cứ phải trong cùng 1 ưng dụng, nó chỉ kô thể gửi thông điệp WM_GETTEXT tới 1 cửa sổ có đặt flag ES_PASSWORD mà nằm ngoài ứng dụng gọi nó thôi, do vậy nếu ta chỉ dùng hàm SendMessage với mục đích khác thì kô cần xài Hook đâu.
(*)flag ES_PASSWORD có tác dụng biến các ký tự nhập vào thành 1 ký tự đặc biệt duy nhất, thông thường là * ví dụ các pác gõ abcd thì nó hiển thị là ****
còn thằng autoplay em cũng chưa tim hiểu nhiêu nên chưa dám nói [IMG]images/smilies/smile.png[/IMG], hẹn hum sau sẽ nói tiếp [IMG]images/smilies/biggrin.png[/IMG]
cái này ngoài lề thôi, nó kô liên quan j đến Hook cả, nhưng nó sẽ giúp các pác tìm HANDLE 1 cách dễ dàng
_http://msdn.microsoft.com/en-us/magazine/cc301495.aspx
Bài 3:
tiếp theo bài này em xin viết về cơ chế của AutoPlay
các pm autoplay bây h đọc thông tin từ bộ nhớ game và dùng hook để thay đổi các thông tin cần thiết.
đến đây em chia thành 2 phần, phần 1 là đọc thông tin từ bộ nhớ game, phần 2 là ghi thông tin.
phần 1: đọc thông tin game
phần này thì kô cần dùng đến Hook vì bản thân ta kô tác động gì đến game cả ("đọc" theo đúng nghĩa đen của nó)
các thông tin trong bộ nhớ game được lưu trong các địa chỉ (có thể tĩnh hoặc động), vấn đề ở đây chỉ là làm sao "lấy" được các địa chỉ cần thiết và "đọc" nó như thế nào???
để "lấy" đc các địa chỉ cần thiết là rất phức tạp, phải biết debug game may ra mới lấy đc nhưng ma thay các việc đó đã có các bậc tiền bối của chúng ta lo roài [IMG]images/smilies/biggrin.png[/IMG], thay vì phải ngồi hàng h để debug game, ta có thể search trên mạng trong vài giây là dc [IMG]images/smilies/biggrin.png[/IMG]
lấy ví dụ với game VLTK, điều ta quan tâm nhất là thông tin vê char và mob (quái vật)
Tất cả các nhân vật (người chơi, quái, Npc...) được lưu thông tin trong mảng gồm 256 đối tượng, mỗi đối tượng kích thước 0x7E4C, địa chỉ lưu địa chỉ của mảng trên là 0x00D3A570. Đối tượng 0 để trống, đối tượng 1 là người chơi, còn lại là quái & Npc.
như vậy ta đã có đc các địa chỉ cần thiết rồi (trong mảng trên thì phần tử a[1] lưu thông tin về người chơi, các phân tử còn lại(từ a[2] đến a[255]) lưu thông tin về mob. mảng a có địa chỉ tĩnh là 0x00D3A570).
với các game khác thi các pác tự search trên google sẽ thấy thôi
bi h đến bước thứ 2 là đọc dữ liệu như thế nào?
tất cả các nhân vật(người chơi, mob, npc...) đều thuộc cùng 1 lớp chung(không biết các nhà lập trình game gọi thế nào nhưng em tạm gọi đó là class CNpc
cấu trúc của class này tùy từng game sẽ khác nhau, ở đây em xin lấy ví dụ về game VLTK
Mã:class CNpc{ ... DWORD m_NpcKind; //0x0020 1=mod, 1=player,... ... DWORD m_Doing; //0x00EC 1=stand, 2=walk, 3=run... ... int m_CurLife; //0x0B44 int m_CurLifeMax; int m_CurLifeRep; int m_CurMana; int m_CurManaMax; ... BOOL m_bRideHorse; //0x0D58 char Name[32]; //0x0D5C Tên nhân vật int m_n***; ... BOOL m_FightMode; //0x0EAC };
để đọc dữ liệu trong bộ nhớ game các pác dùng hàm API ReadProcessMemory
nói qua về hàm này, hàm này có 5 tham số, tham số đâu tiên là handle của game vltk, tham số thứ 2 là địa chỉ offset mà ta sẽ lấy ra để đọc, tham số thứ 3 là buffer ta dùng để đọc dữ liệu, tham số thứ 4 là kích thước của dữ liệu ta sẽ đổ vào buffer, tham số cuối kô cân xét, cứ để NULL
sau đây là các bước thực hiện chi tiết
B1: tìm handle của process của game vltk, các pác có thể tham khảo cách tìm process ở đây: http://www.codeproject.com/KB/threads/getprocessid.aspx
B2: dùng hàm ReadProcessMemory để đọc các thông tin cần thiết
đây là ví dụ với game vltk
Mã:CNpc Npc;LPBYTE lpBaseAdd, lpCurAdd;//đọc địa chỉ đầu của mảng NpcReadProcessMemory(m_hVLProcess, (LPBYTE)NPC_BASE_ADD, (LPVOID)&lpBaseAdd, 4, NULL);//tính toán địa chỉ của ngươi chơi (PLAYER_INDEX = 1)lpCurAdd = lpBaseAdd + PLAYER_INDEX*NPC_DATA_SIZE;//đọc thông tin vê ngươi chơiReadProcessMemory(m_hVLProcess, lpCurAdd, (LPVOID)&Npc, sizeof(CNpc), NULL);...
lpBaseAdd là địa chỉ của mảng Npc, lpCurAdd là địa chỉ hiện tại, có đc băng cách + lpBaseAdd với (chỉ số trong mảng)*NPC_DATA_SIZE, NPC_DATA_SIZE chính là kích thước của 1 đối tượng trong mảng = 0x7E4C (đã nói ở trên), ở đây nếu muốn đọc thông tin của player thi + lpBaseAdd với 1*0x7E4C (do chỉ số của player trong mảng = 1)
như vậy la ta đã có thông tin cơ bản về người chơi, mob...
ngoài ra có thể sử dụng thêm 1 vài hàm API để rao, bán đồ
Mã:void PostChatMessage(LPCTSTR szChatMsg){ //đặt focus cho cửa sổ chat ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_RETURN, 0x001C0001); //Xóa nội dung trong cửa sổ chat ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_DOWN, 0x00500001); while (szChatMsg[0]) { ::PostMessage(m_hVLWin, WM_CHAR, LOBYTE(szChatMsg[0]), 0); szChatMsg++; } //đặt focus cho cửa sổ game ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_RETURN, 0x001C0001);}
để tìm handle của game vltk thì các pác chỉ cần dùng hàm FindWindow( )
như vậy đến đây là quá đủ để làm cái AutoSell + AutoBuff máu& mana rồi đó!
Bài 4:
về vấn đề lấy Handle của cửa sổ và process (tiến trình) ở phần trên em viết hơi rời rạc, để em tổng hợp lại cho các pác tiện theo dõi
I. Lấy handle của cửa sổ chính (main window)
- dùng hàm FindWindow( ) là đơn giản nhất, để sử dụng hàm này chỉ cần biết đc title của cửa sổ đó, ví dụ đối với game vltk thì title là Vo Lam Truyen Ky
II. Lấy handle của cửa sổ con, ví dụ lấy handle của edit text chứa password trong game vltk
ở đây ta dùng hàm FindWindowEx( )
prototype: FindWindowEx(handle của CS cha, handle của cửa sổ con, tên của class của cửa sổ con, tên của cửa sổ con)
các pác để ý tham số thứ 2, đây chính là handle của cửa sổ con trước đó tính theo trục Z, nếu để NULL thì máy sẽ tìm từ trên cùng xuống dưới.
Vì trong các cửa sổ con có thể có thêm cửa sổ con (con của con[IMG]images/smilies/biggrin.png[/IMG]) nữa vì thế cần 1 vòng lặp while ở đây để duyệt tất cả các cửa sổ con nằm trong main window.
ví dụ ở đây cần tìm các cửa sổ con thỏa mãn đk chúng là edit text.
B1 tìm handle của main window
B2 duyệt lần lượt các cửa sổ con ( có lẽ phải dùng đệ quy )
Mã:HWND hMainWnd; //lưu handle của cửa sổ chínhHWND hTemp; //lưu handle của 1 cửa sổ con hMainWnd = FindWindow(NULL,"Vo Lam Truyen Ky");if (hMainWnd == NULL) //chưa khởi động game???return;
Về phần duyệt đệ qui thì nó cũng giống như duyệt cái Window Explorer thôi, cứ duyệt lần lượt từ trên xuống dưới, duyệt đến đâu lại tim cửa sổ con của nó...
III.lấy handle của 1 process(tiến trình)
tham khảo tại đây
_http://www.codeproject.com/KB/threads/getprocessid.aspx
uốn chặn một cách đầy đủ, chính xác
-
25-02-2011, 10:11 AM #4Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
(Tiếp , Sưu tầm trên blog Benina )
003: _http://rootbiez.blogspot.com/2009/11/programming-ung-dung-co-che-hook-phan-2.html
[Programming] Ứng dụng cơ chế Hook - Phần 2
Ứng dụng cơ chế Hook để xây dựng chương trình hỗ trợ gõ Tiếng Việt trên Hệ Điều Hành Windows 32 Bit - Phần 2
Phần II : Ứng Dụng Hook Để Xây Dựng Bộ Gõ Tiếng Việt
I.Đặt vấn đề
Nhúng tiếng Việt vào các ứng dụng chạy trên nền Windows luôn là vấn đề được nhiều người quan tâm. Và thực tế đã có khá nhiều tổ chức, cá nhân đưa ra các sản phẩm thuộc dạng này. Tính năng chủ yếu của những phần mềm này là cho phép gõ tiếng Việt trong các môi trường soạn thảo của ứng dụng. Bên cạnh đó, một số tác giả còn tích hợp thêm các chức năng khác như chuyển đổi font, kiểm tra chính tả,... Có thể thấy VietWare, VietKey là những phần mềm tiêu biểu cho lĩnh vực này, và trong thực tế, chúng đã đáp ứng được nhu cầu của phần lớn người sử dụng máy vi tính.
II.Phương án giải quyết cho font ABC (.VnTime,…)
II.1.Giới thiệu font ABC
Đây là loại font 8 bit có các ký tự được thiết kế sẵn gồm cả dấu, không giống như loại font 2 byte có các ký tự chữ và ký tự dấu riêng. Bảng mã 8 bit có tất cả 256 ký tự, trong đó có 128 ký tự chuẩn không thể sửa đổi. Tuy nhiên, do tiếng Việt có 146 ký tự nên 128 ký tự còn lại của bảng mã này không đủ chỗ, đó là lý do làm cho font ABC có một số ký tự vi phạm các ký tự điều khiển chuẩn, và phải tách ra làm 2 file font cho chữ thường và chữ hoa của cùng một loại font. Điều này là một trong những điều bất tiện trong việc sử dụng loại font này.
II.2. Ý tưởng chính
Hãy bắt đầu bằng tình huống đơn giản sau : người dùng gõ phím ‘a’, tiếp theo là phím ‘1’ và ta đang dùng kiểu gõ VNI. Điều ta mong muốn lúc này là ký tự ‘a’ trên màn hình sẽ biến mất, thay vào đó sẽ là ký tự ‘á’.
Một ý tưởng tự nhiên là cài đặt một hook WH_GETMESSAGE để hứng thông điệp WM_CHAR. Cách thực hiện như sau :
· Hứng thông điệp WM_CHAR
· Kiểm tra ký tự hiện tại là ‘1’, ký tự trước là ‘a’
· Nếu đúng :
· Dùng SendMessage gởi WM_CHAR với ký tự xoá
· Sửa ký tự ‘1’ thành ký tự ‘á’
· Nếu sai :
· Không xử lý
Ở đây phát sinh một vấn đề cần xem xét : có chắc là message WM_CHAR ta gởi sẽ đến được ứng dụng ? Với cách làm trên thì chỉ đúng với các ứng dụng đơn giản như Notepad, bởi thực chất Notepad chỉ là một EDIT_TEXT control được gắn vào một cửa sổ khung. Các thông điệp được gởi sẽ trực tiếp đến ứng dụng. Còn đối với các môi trường soạn thảo lớn và phức tạp hơn như WordPad hay Microsoft Word, chúng nhận thông điệp từ các sự kiện của người dùng qua nhiều cấp, do đó khi ta gởi message đến, không có gì đảm bảo message của chúng ta sẽ đến chính xác nơi cần đến.
Còn bây giờ hãy xét cách tiếp cận sau đây : thay vì dùng các hàm để gởi thông điệp đến ứng dụng, ta sẽ làm phát sinh thông điệp một cách tự nhiên hơn, rồi sau đó thực hiện sự sửa đổi trên thông điệp để được ký tự có dấu mong muốn. Vẫn xét tình huống trên, cách làm bây giờ như sau :
· Hứng message WM_KEYDOWN
· Kiểm tra phím hiện tại là ‘1’, phím trước là ‘a’ (giá trị mã phím ảo)
· Nếu đúng :
· Sửa phím ‘1’ thành phím xóa (mã phím ảo VK_BACK).
· Nếu sai :
· Không xử lý.
· Hứng message WM_CHAR
· Kiểm tra ký tự hiện tại là ký tự xoá (có mã 8)
· Nếu đúng:
·Dùng keybd_event để giả lập nhấn phím SpaceBar
·Nếu sai :
· Không xử lý
· Kiểm tra ký tự hiện tại là ký tự khoảng trắng (có mã 32)
· Nếu đúng :
· Sửa ký tự này thành ký tự ‘á’.
· Nếu sai :
·Không xử lý
Tất nhiên quá trình trên cần có một số thao tác với cờ hiệu để phân biệt khi nào phím BackSpace, SpaceBar được người dùng nhấn, khi nào 2 phím này là do chương trình của ta gởi đến. Tuy nhiên, để dễ thấy được ý tưởng chủ đạo của giải thuật vừa nêu, ta đã không đưa vào các thao tác này ở phần trên.
Giải thích :
Ý tưởng chính xuyên suốt giải thuật trên là thao tác giả lập gõ phím để phát sinh thông điệp trung gian, sau đó hứng lại thông điệp trung gian và thực hiện sửa đổi để có được ký tự mong muốn. Phím được chọn để giả lập trong trường hợp này là SpaceBar (Về nguyên tắc có thể chọn bất kỳ phím nào cho mục đích này, miễn là phím đó khi gõ có thể cho ta message WM_CHAR với mã ký tự hợp lệ).
Có thể hình dung sự hoạt động của giải thuật trên như sau :
· Khi người dùng gõ phím ’a’, Filter Function không làm gì cả.
· Khi phím ‘1’ được gõ, điều kiện ở nhánh chặn message WM_KEYDOWN được thỏa, phím ‘1’ chuyển thành phím xóa (mã phím ảo VK_BACK) . Chính phím xóa này sẽ thực hiện thao tác xóa chữ ‘a’ trên màn hình soạn thảo.
· WM_KEYDOWN đã được sửa đổi ở trên được chuyển thành WM_CHAR với mã ký tự 8, thoả điều kiện ở nhánh hứng WM_CHAR, SpaceBar được nhấn nhờ hàm keybd_event.
· Thông điệp WM_KEYDOWN do phím SpaceBar sinh ra không bị chặn, tiếp tục chuyển thành WM_CHAR với mã ký tự 32. Lúc này, nó được Filter Function hứng lại, và sau khi kiểm tra các điều về cờ hiệu, Filter Function chuyển mã ký tự của message này thành mã ky tự của chữ ‘á’. Kết quả ta có là chữ ‘a’ bị xóa và chữ ‘á’ sẽ xuất hiện trên màn hình.
Rõ ràng cách làm này an toàn hơn so với cách đầu tiên, bởi lẽ message được phát sinh y hệt như khi ta gõ phím thực sự (do được giả lập bằng hàm keybd_event), và chuyện lấy được các message này là chuyện của ứng dụng (bởi ta đã làm phát sinh message ở mức thấp), ta không cần phải can thiệp bằng lệnh SendMessage như phương án đầu tiên.
Để thực hiện điều này, dĩ nhiên ta phải tổ chức Filter Function của hook WH_GETMESSAGE để có thể hứng được cả 2 thông điệp WM_KEYDOWN và WM_CHAR.
II.3.Giải thuật chi tiết và cài đặt
Dễ nhận thấy việc đầu tiên là phải ghi nhận các mã ký tự của các ký tự có dấu trong font ABC để thực hiện việc sửa đổi thông điệp sau này.
Ta sẽ dùng tiện ích Character Map trong bộ System Tool của Windows để ghi nhận các ký tự và mã tương ứng của chúng, sau đó lưu các mã này vào một mảng toàn cục được khai báo trong DLL, lấy tên TCVN3 chẳng hạn. Nên tổ chức mảng này theo một thứ tự nhất định, ví dụ các mã của các ký tự có dấu của cùng một nguyên âm gốc nên đi liên tiếp nhau. Sau đó, phải ghi nhớ các chỉ số của các phần tử trong mảng tương ứng với từng ký tự, ví dụ phần tử mảng có giá trị là mã của chữ ‘á’ sẽ có chỉ số là bao nhiêu…
Đến đây ta cần một biến toàn cục nữa, lấy tên Index chẳng hạn, để lưu các giá trị chỉ số vừa nêu khi người dùng nhấn phím, và các phím này nằm trong tầm vực bỏ dấu của chương trình. Ví dụ, khi họ gõ ‘a’, sau đó gõ ‘1’ thì giá trị của Index là chỉ số của phần tử mảng chứa mã ký tự của chữ ‘á’.
Cách làm này sẽ tiện lợi khi ta thực hiện sửa đổi trên thông điệp WM_CHAR, rõ ràng lúc này giá trị wParam mới của message sẽ là TCVN3[Index].
Khi cài đặt hook chặn message, nhánh chặn WM_KEYDOWN sẽ làm nhiệm vụ kiểm tra phím và xác định Index, chính nhánh hứng message WM_CHAR mới thực sự thi hành tác vụ sửa đổi thông tin trên thông điệp để sinh ra các ký tự có dấu.
Sửa dấu trong khi gõ
Với những thông tin trên , ta đã giải quyết được tình huống nêu ra ở phần đầu của giải thuật. Tất nhiên khi cài đặt ta sẽ áp dụng tổng quát cho các nguyên âm và các phím có thể sinh dấu từ ‘1’ đến ‘5’ (ta gọi phần này là Module_1) .
Nhưng nếu người dùng muốn sửa dấu trong khi gõ thì sao ? Ví dụ, họ muốn có chữ ‘à’, nhưng các phím đã vô tình bị gõ là ‘a’ và ‘1’, lúc này trên màn hình xuất hiện chữ ‘á’, bây giờ khi họ gõ ‘2’, chữ ‘á’ phải bị xoá và thay vào đó là chữ ‘à’. Ta hãy xem giải pháp cho bài toán này :
· Hứng message WM_KEYDOWN
· Kiểm tra nếu phím hiện tại (CurrentKey) là các phím từ ‘1’ đến ‘5’, phím trước (Prev1Key) cũng cùng dạng này, và phím trước nữa (Prev2Key) là nguyên âm.
· Nếu đúng :
· Sửa phím hiện tại thành phím xóa (mã phím ảo VK_BACK)
· Giả lập gõ 2 phím sau :
· Prev2Key
· CurrentKey
· Nếu sai :
· Không xử lý
Giải thích (Xét trong ngữ cảnh tình huống nêu trên):
Phím ‘2’ được sửa thành phím xóa sẽ thực hiện xóa ký tự ‘á’ trên màn hình. Sau đó 2 phím được giả lập sẽ tạo ra chữ ‘à’ như ta mong muốn. Ở dây ta thấy rõ hơn ý tưởng hứng lại sự kiện để xử lý : chỉ cần xoá ký tự sai, và giả lập gõ chính xác để tạo ký tự đúng. Lúc này chính Module_1 ta đã viết sẽ tiếp nhận các sự kiện giả lập và nó lại thi hành các tác vụ như ta đã mô tả : kiểm tra điều kiện để xác định Index, sửa message,… để cho ra ký tự mà người dùng muốn gõ.
II.4. Áp dụng cho các luật bỏ dấu khác
Với các ý tưởng trên, các luật bỏ dấu, sửa dấu trong khi gõ khác (bỏ dấu ô, ơ, ư, â, ă, ấ,…) được thực hiện với cách thức hoàn toàn tương tự. Có khác chăng là các điều kiện kiểm tra giá trị các phím trước phím hiện tại để xác định Index cho phù hợp.
II.5. Ưu và nhược điểm của giải pháp
Ta có thể nhận ra ưu điểm của giải pháp này ở tính đơn giản và an toàn của nó. Đơn giản trong quá trình cài đặt, mang tính tái sử dụng ; an toàn trong quá trình phát sinh các message cho ứng dụng.
Tuy nhiên, cách làm này chỉ áp dụng được dễ dàng cho bài toán bỏ dấu, sửa dấu ngay sau nguyên âm mà thôi. Còn đối phó với vấn đề bỏ dấu tự do (bỏ dấu theo vần, theo từ), phương án này cần các thao tác kiểm tra rất phức tạp trên tập các giá trị phím trước, để xác định chính xác vị trí bỏ dấu. Bởi lẽ, nó phải duyệt trên giá trị của các mã phím ảo để xác định các nguyên âm có dấu, và điều này là rất khó khăn. Ta sẽ có một cách tiếp cận khác ưu việt hơn cho bài toán này ở phần kế sau đây, cách tiếp cận thao tác trực tiếp tên các ký tự.
III.Phương án giải quyết cho font VNI-Win (VNI-Times,...)
III.1.Cấu trúc font VNI-Win
Khác với các font ABC(font 1 byte) có dấu và các nguyên âm được tổ hợp trong cùng một ký tự, font VNI-Win(font 2 byte) có dấu và các nguyên âm rời nhau. chẳng hạn ký tự ‘á’ là sự tổ hợp của hai ký tự ‘a’ và dấu sắc. Tuy nhiên, có một số ký tự do có cấu trúc đặt biệt nên dấu và ký tự được tổ hợp trong một ký tự như: đ, Đ, í, ì, ỉ, ĩ, ị, ỵ, Í, Ì, Ỉ, Ĩ, Ị, Ỵ, ư, ơ, Ư, Ơ.
Chúng ta hãy bước vào phần sau để xem xét các phương án cho bộ gõ tiếng việt dùng cho font VNI-Win.
III.2.Kiểu gõ dấu tại chổ
Như đã đề cập ở phần trước, để xây dựng bộ gõ cho các phần mềm trong môi trường Windows ta sẽ cài đặt một hàm Hook có chức năng chặn ngắt bàn phím ở mức toàn cục. Và khi đó tất cả các phím nhấn từ bàn phím sẽ được chương trình gõ phím của chúng ta xử lý trước khi chúng được gởi tới ứng dụng. Như vậy để gõ được tiếng Việt trong các dụng dụng đang thi hành trong môi trường trong hàm Hook phải thực hiện thao tác thay đổi các ký tự khi người dùng gõ vào, nhờ đó ứng dụng sẽ nhận được các ký tự có dấu tiếng Việt.
Giải thuật
Ta sẽ cài đặt hàm Hook WH_GETMESSAGE hứng thông điệp WM_KEYDOWN và WM_CHAR để xử lý. Nhắc lại, khi người dùng nhấn phím ta sẽ lần lượt nhận được các thông điệp WM_KEYDOWN, WM_KEYUP, WM_CHAR. Chương trình ta sẽ xử lý ở thông điệp WM_KEYDOWN và WM_CHAR.
Nguyên tắc khi người dùng nhấn một phím nằm trong phạm vi là phím bỏ dấu(có khả năng là phím dấu), 0-9 đối với kiểu gõ VNI và “s,f,r,x,j,a,o,e,w,d,z” đối với kiểu gõ TELEX. Ta xét ký tự trước đó, có 3 khả năng xảy ra:
· Nếu là ký tự có thể bỏ dấua,e,i,o,u,y,d) nếu dấu phù hợp với kí tự cần bỏ dấu (ký tự liền trước) ta tiến hành bỏ dấu.
· Nếu là một ký tự dấu rời hoặc một ký tự nguyên âm có dấu( chẳng hạn như “í” chỉ là một ký tự còn “á” là hai ký tự) ta xét là nên sửa dấu hay lập dấu và sau đó điều chỉnh lại cho phù hợp.
· Nếu là một phụ âm thì ta không xử lý gì cả.
Lưu ý ở đây có điểm khác biệt giửa font 1 byte và font 2 byte, khi bỏ dấu chẳng hạn chử “á” font 1 byte sẽ xóa ký tự “a” trước đó bằng cách sửa ký tự “1” vừa nhận được thành ký tự xóa VK_BACK và sau đó gởi một ký tự “á” còn font 2 byte sẽ gởi 2 ký tự “a” và dấu sắc sau khi xóa ký tự “a” trước đó.
Bài toán lúc này trở nên đơn giản hơn và vấn đề lúc này là chương trình sẽ xử lý dấu như thế nào cho đơn giản và chính xác. Trước tiên ta cần phân biệt xử lý đối với các lọai dấu khác nhau:
- loại 1: gồm các dấu: sắc, huyền, hỏi, ngã, nặng.
- loại 2: gồm các dấu: dấu “^” và dấu “ă”.
- lọai 3: gồm các dấu: dấu “^” kết hợp với một dấu loại 1(ví dụ: “ấ”, “ồ”, “ể”...)
- loại 4: gồm các dấu: dấu “ă” kết hợp với một dấu loại 1(ví dụ: “ắ”, “ẵ”, “ặ”...”)
Bài toán xử lý gõ tiếng việt cho font chữ 2 byte theo kiểu gõ cho phép bỏ dấu ngay đã được giải quyết thành công.
Hàm Hook xử lý cho bài toán gõ tiếng việt font 2 byte kiểu gõ bỏ dấu tại chổ.
Mã:EXPORT LRESULT CALLBACK HookProc2ByteNormalVNI(int nCode, WPARAM wParam, LPARAM lParam){ tagMSG *pMsg; if (nCode>=0 ) { pMsg = (MSG*)lParam; if (wParam == PM_REMOVE) { if (pMsg->message == WM_KEYDOWN) { if (InScope(pMsg->wParam)||(pMsg- >wParam==VK_NUMPAD0&&DeleteTwo)) { switch (pMsg->wParam) { case VK_1: case VK_2: case VK_3: case VK_4: case VK_5: ProcessSign1(pMsg->wParam-VK_0); if (Del) pMsg->wParam = 8; break; case VK_6: case VK_8: ProcessSign2(pMsg->wParam-VK_0); if (Del) pMsg->wParam = 8; break; case VK_7: if Prev1Key==VK_o||Prev1Key==VK_O||Prev1Key==VK_u||Prev1Key==VK_U) { pMsg->wParam = 8; Del = TRUE; Flag = TRUE; switch (Prev1Key) { case VK_o: NextKey = o7; break; case VK_O: NextKey = O7; break; case VK_u: NextKey = u7; break; case VK_U: NextKey = U7; break; } } break; case VK_9: if (Prev1Key==VK_d||Prev1Key==VK_D) { pMsg->wParam = 8; Del = TRUE; Flag = TRUE; if (Prev1Key == VK_d) NextKey = a9; else NextKey = A9; } break; case VK_NUMPAD0: if (DeleteTwo) { pMsg->wParam = 8; Del = TRUE; Flag = TRUE; DeleteTwo = FALSE; } } } } if (pMsg->message == WM_CHAR) { if (Flag) { switch (pMsg->wParam) { case VK_1: case VK_2: case VK_3: case VK_4: case VK_5: case VK_6: case VK_7: case VK_8: case VK_9: if (!Del) { pMsg->wParam = NextKey; Flag =FALSE; } break; case VK_BACK: if (Del) keybd_event(VK_NUMPAD0, 0x45, KEYEVENTF_EXTENDEDKEY, 0); break; case VK_0: if (Del) { pMsg->wParam = NextKey; Flag = FALSE; } break; }//of switch }//of if (Flag) if (!Del) // Normal { Prev2Key = Prev1Key; Prev1Key = pMsg->wParam; } else { Prev1Key = NextKey; Del=pMsg->wParam ==8; } }//of if (pMsg->message==WM_CHAR) } } // of if (nCode >= 0) return CallNextHookEx(hhook, nCode, wParam, lParam);}
Khi gõ theo kiểu bỏ dấu tại chổ có những mặt hạn chế: thứ nhất cách gõ không được tự nhiên thoải mái như khi viết và bỏ dấu bằng tay, thứ hai rất khó cho người dùng sửa dấu sau khi đã đánh sang ký tự khác, thứ ba có thể người dùng sẽ bỏ dấu không đúng chính tả hay không đúng vần. Vấn đề dặt ra ờ đây là làm thế nào để hỗ trợ được những tính năng khiếm khuyết trên.
Giải pháp gõ dấu sau
Một ví dụ cụ thể về gõ dấu sau, chẳng hạn như khi người dùng gõ từ “làng” thì họ có thể gõ theo nhiều cách khác nhau như: la2ng, lan2g, lang2.
· Giải pháp ban đầu
Giải pháp ban đầu được đưa ra là sử dụng Clipboard, sau khi người dùng gõ một phím có khả năng là phím dấu ta giả lập một phím Shitf và các phím mủi tên ß để đánh dấu khoảng 8 ký tự vừa gõ, sau đó giả lập tổ hợp phím Ctrl X để cắt chuổi 8 ký tự được gõ trước ký tự hiện hành vào Clipboard, tiếp theo ta sẽ lấy dữ liệu ra từ Clipboard và đổ vào mảng ký tự và xử lý dấu trên chúng, dữ liệu được xử lý xong được đưa vào Clipboard, cuối cùng ta sẽ giả lập một tổ hợp phím Ctrl V để dán chuổi vừa xử lý ra màn hình ứng dụng.
Việc áp dụng Clipboard theo hương tiếp cận này dẫn đến nhiều khiếm khuyết và khó khăn khi xử lý. D không phải tất cả các ứng dụng đều dùng phím Shift và các phím mủi tên để đánh dấu, và dùng phím Ctrl X, Ctrl V để cắt và dán. Hơn nửa, mổi khi một phím trong phạm vi xử lý dấu được nhấn thì ứng dụng phải thực hiện đồng thời 2 thao tác cắt và dán, việc này làm cho chương trình trở chậm chạp, và màn hình của ứng dụng chớp, nhấp nháy trong vùng bị cắt và dán lại. Một ý kiến hay là chỉ nên áp dụng Clipboard cho trường hợp dùng Sửa dấu nhanh(Chức năng này hổ trợ cho người sử dụng khi họ đã hoàn tất văn bản và kiểm tra lại văn bản từ đầu đến cuối một lần và sửa lỗi).
· Giải pháp khác
Một giải pháp khác là dùng một buffer thay thế cho Clipboard, tại mỗi thời điểm buffer sẽ lưu giữ giá trị của 8 ký tự(từ dài nhất) trước đó. Khi người dùng gõ một ký tự trong phạm vi bỏ dấu, ta sẽ cập nhật lại buffer cho phù hợp, xóa một số ký tự cần thiết và xuất một số ký tự đã được cập nhật trong buffer.
Ví dụ:
Khi gõ : lang giá trị buffer sẽ là:
l
a
n
g
Khi gõ thêm ký tự: 2, có nghĩa là bỏ dấu huyền. buffer sẽ được cập nhật lại như sau:
l
a
\
n
g
Và khi đó ta phải xóa ngược lại 3 ký tự(xóa “ang”) và xuất ra 4 ký tự cuối của buffer(xuất “àng”).
Như vậy mổi lần người dùng gõ dấu ta phải hiệu chỉnh lại buffer và xác định số ký tự cần phải xóa và số ký tự cần xuất ra từ buffer(tính từ vị trí cuối cùng).
Đối với các ký tự khác không phải bỏ dấu thì ta không xử lý nhưng vẫn phải cập nhật lại trong buffer.
Ví dụ:
Các trạng thái của buffer khi gõ chữ “làng”.
Gõ “l”
l
Gõ “a”
l
a
Gõ “n”
l
a
n
Gõ “g”
l
a
n
g
Gõ “2”
l
a
\
n
G
Lúc này DelNum = 3(số ký tự cần xóa) và OutNum =4(số ký tự cần xuất).
Đến đây thì vấn đề gõ dấu sau được giải quyết khá hoàn hảo. Do sử dụng buffer nên chương trình xử lý nhanh hơn, và tại mổi thời điểm chỉ xóa đi số ký tự tố thiểu cần thiết nên giúp cho màn hình ứng dụng không bị nhấp nháy.
· Nẩy sinh vấn đề
Tuy giải pháp trên giải quyết rất tốt vần đề đặt ra nhưng bên cạnh đó lại nẩy sinh ra một khó khăn khác là làm thế nào để xác định vị trí bỏ dấu trong một vần tiếng việt, đây là vấn đề khó khăn và phức tạp bởi vì trong tiếng việt không có quy luật bỏ dấu cụ thể, chuẩn mực. Cuối cùng đưa đến việc xây dựng bộ luật bỏ dấu dành cho tiếng Việt.
· Bộ luật bỏ dấu tiếng Việt
Do trong tiếng việt dấu chỉ được bỏ ở các vị trí của các nguyên âm nên bộ luật sẽ được xây dựng dựa trên các nguyên âm, chú ý rằng trong một từ tiếng việt các nguyên âm luôn đi liền nhau và số lượng nguyên âm trong một từ không thể quá 3 nguyên âm. Với hai nhận xét trên giúp ta có thể xây dựng bộ luật bỏ dấu tiếng Việt một cách dễ dàng hơn.
Phân chia các từ theo số lượng nguyên âm:
Các từ có 1 nguyên âm:
Nếu ký tự đứng trước nguyên âm là ký tự “q” hoặc “p” thì không được phép bỏ dấu.
Ngược lại dấu sẽ được bỏ tại vị trí nguyên âm duy nhất này.
Các từ có 2 nguyên âm:
Nguyên âm đầu: a
A
vị trí bỏ dấu
AE
không có
AI
A
AO
A
AU
A
AY
A
Nguyên âm đầu: e
E
vị trí bỏ dấu
EA
không có
EI
không có
EO
E
EU
E
EY
không có
Nguyên âm đầu: i
I
vị trí bỏ dấu
IA
I
IE
E
IO
không có
IU
I
IY
không có
Nguyên âm đầu: o
O
vị trí bỏ dấu
OA
O
OE
O
OI
O
OU
không có
OY
không có
Nguyên âm đầu: u
U
vị trí bỏ dấu
UA
U
UE
E
UI
U
UO
O
UY
U
Nguyên âm đầu: y
Y
vị trí bỏ dấu
YA
Không có
YE
Không có
YI
Không có
YO
Không có
YU
Không có
Theo sơ đồ xử lý bên dưới.
Các từ có 3 nguyên âm:
Có các tổ hợp 3 nguyên âm sau:
ươu rượu
ieu liễu
oai hoài
uay khuấy
uoi muỗi
uya khuya
uye khuyên
yeu* yêu
(*Đây là dạng đặt biệt được sử lý riêng, vần yêu chỉ đúng trong trường hợp đứng riêng lẻ không đi cùng với phụ âm đứng trước)
Thông thường các từ có 3 nguyên âm thường được bỏ dấu ở nguyên âm giữa, chỉ trừ trường hợp vần “uyê” được bỏ dấu ở nguyên âm cuối.
Trường hợp đặc biệt nếu bắt đầu bằng “qu” hoặc “gi” thì dấu sẽ bỏ ở một trong hai nguyên âm sau được xử lý như từ có hai nguyên âm.
SƠ ĐỒ XỬ LÝ DẤU CHO CÁC TỪ CÓ 2 NGUYÊN ÂM:
· Nếu nguyên âm đầu là:
“a” thì
nếu nguyên âm sau là “a”,”e”:không bỏ dấu
ngược lại:bỏ dấu tại nguyên âm đầu a.
“e” thì
nếu nguyên âm sau là “o”,”u”:bỏ dấu tại nguyên âm đầu e.
ngược lại:không bỏ dấu
“i” thì
nếu trước “i” là phụ âm “g”:bỏ dấu ở nguyêm âm sau.
ngược lại
nếu nguyên âm sau là “i”,“o”,”y”:không bỏ dấu
ngược lại
nếu nguyên âm sau là “a”,”u”:bỏ dấu ở nguyên âm đầu.
ngược lại:bỏ dấu ở nguyên âm sau
“o” thì
nếu nguyên âm sau là “o”,”u”,”y”:không bỏ dấu
ngược lại:bỏ dấu ở nguyên âm đầu.
“u” thì
nếu trước “u” là phụ âm “q”:bỏ dấu ở nguyêm âm sau.
ngược lại
nếu nguyêm âm sau là “a”,”i”,”y”:bỏ dấu ở nguyên âm đầu
ngược lại
nếu nguyên âm sau là “e”,”o”:bỏ dấu ở nguyên âm sau
ngược lại:không bỏ dấu.
“y” thì không bỏ dấu.
Vấn đề giải quyết được có vẻ phức tạp hơn nhưng với bộ luật xử lý như trên ta có thể cài đặt bộ gõ có chức năng kiểm tra lổi chính tả, không cho phép gõ những từ sai lỗi chính tả và có khả năng bỏ dấu sau rất tốt. Chắc bạn sẽ rất ngạc nhiên nếu như tôi đưa ra một vấn đề vướng mắc khác cho giải pháp gần như hoàn hảo này. Thật vậy, khi bạn gõ “hoa” bạn gõ thêm dấu huyền dĩ nhiên bộ gõ này sẽ bỏ dấu ngay nguyên âm “o” trở thành “hòa” hoàn toàn chính xác. Nhưng điều gì sẽ xảy ra nếu bạn gõ thêm “n” nó sẽ trở thành “hòan” đây là một điểm mà một số bộ gõ phím như VietWare, VietKey,... và các bộ gõ phím thông dụng khác chưa xử lý.
Vì vậy để chương trình gõ phím dễ dùng, tiện dụng và thông minh hơn, bạn nên cài thêm chức năng sửa dấu để điều chỉnh dấu khi phát hiện dấu đã được bỏ sai vị trí.Điều này cũng được thực hiện dễ dàng nhờ vào bộ luật mà chúng ta đã xây dựng từ trước nhưng phải kết hợp thêm một số điều kiện ngữ cảnh, có nghĩa là việc xác định vị trí bỏ dấu không chỉ phụ thuộc vào các nguyên âm mà còn phụ thuộc vào các phụ âm trước hoặc sau các nguyên âm đó.
-
03-12-2011, 01:01 PM #5Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
hay wa. [IMG]images/smilies/laughing.gif[/IMG] sao không có ai like thế này thank mấy anh mong chia sẽ nhiều cho đàn em học hỏi
-
06-04-2012, 08:44 AM #6Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
Hi all,
muốn hook multiple key trên keyboard thì làm sao vậy mọi người (Ctrl + .....)?
-
17-04-2012, 04:12 PM #7Junior Member
- Ngày tham gia
- Sep 2015
- Bài viết
- 0
Bạn ơi nếu sử dụng hook trong xử lý chuột để khi mình kéo và giữ chuột trên màn hình thì sẽ xác lập hình ảnh trên vùng màn hình mình vừa kéo ấy thì phải làm thế nào hả bạn
Không chỉ dừng lại ở việc sử dụng nguyên liệu cao cấp, mỗi công trình còn được "chăm sóc" bằng 4 dịch vụ xuất sắc của Tre Nghệ. Từ việc tư vấn, thiết kế đến thi công và hoàn thiện, mọi quy trình đều...
Chất Lượng và Sáng Tạo: Kiến Trúc...