轉自:http://blog.sina.com.cn/s/blog_455d7a320100vr37.html
前天無聊,翻翻自己的興趣項目文件夾,發現了這個放下很久的項目!那是大三時候的事了。當時是為了提高我魔獸三的按鍵速度,用了個叫移花接木的軟件,把鍵盤的鍵位改了。的確是有所幫助,但這是共享軟件,用40次就不能再用了除非注冊。於是乎就有了做一個類似的軟件出來,在網上搜索了一把發現WinIo模擬按鍵是最可靠的了,就決定向這方向鑽進去了。哎...技術不夠,看了很久的技術文章和代碼也只是了解了一點,而且那時MFC技術還沒到家根本做不出什么能用的東西來...后來聽個兄弟說:反匯編破解。於是又試了下,居然真的被我破解了!一直用到現在啊!haha。
想完當年了,正題!
要做到驅動級的模擬,那么就要像驅動那樣直接對硬件芯片的數據讀寫!這才算牛B!
1.首先,說說這個WinIo到底是何物!
WinIO庫允許在32位的Windows應用程序中直接對I/O端口和物理內存進行存取操作。通過使用一種內核模式的設備驅動器和其它幾種底層編程技巧,它繞過了Windows系統的保護機制。
因為操作系統對一些內存地址進行了保護,這不至於任何程序都能直接訪問硬件地址或胡亂改動系統的內存數據!而我們的鍵盤的端口地址正正在這段地址以內,所以我們不能隨便地訪問到!
但用了WinIo庫就可以了!它提供了很大的幫助!(它是一個國外牛人寫的這是他的主頁http://www.internals.com/,似乎很久沒更新的)
2.有工具了,我們要怎么去開工呢?
別急先看看原理,耐心是必需的!先看看別人寫的參考文章!
ps/2 鍵盤硬件概述
對於驅動來說,和鍵盤相關的最重要的硬件是兩個芯片。一個是 intel 8042 芯片,位於主板上,CPU 通過 IO 端口直接和這個芯片通信,獲得按鍵的掃描碼或者發送各種鍵盤命令。另一個是 intel 8048 芯片或者其兼容芯片,位於鍵盤中,這個芯片主要作用是從鍵盤的硬件中得到被按的鍵所產生的掃描碼,與 i8042 通信,控制鍵盤本身。
當鍵盤上有鍵被按下時,i8048 直接獲得鍵盤硬件產生的掃描碼。i8048 也負責鍵盤本身的控制,比如點亮 LED 指示燈,熄滅 LED 指示燈。i8048 通過 ps/2 口和 i8042 通信,把得到的掃描碼傳送給 i8042。CPU 通過讀寫端口,可以直接把 i8042 中的數據讀入到 CPU 的寄存器中,或者把 CPU 寄存器中的數據寫入 i8042 中。ps/2 口一共有 6 個引腳,可以拔下 ps/2 插頭看一下,這 6 個引腳分別為,時鍾,數據,電源地,電源+5V,還有2個引腳沒有被用到。由於只有一個引腳傳輸數據,所以 ps/2 口上的數據傳輸是串行的。
下面幾幅圖是一個鍵盤的內部,可以看到用來產生掃描碼的按鍵矩陣( Key Martix ),可以看到鍵盤中的芯片(這里不是i8048,是一個兼容的其他型號的芯片)。
詳細的圖見:http://jiurl.nease.net/document/KbdDriver/JiurlKbd 1.htm
注意,i8042 並不一定在主板上單獨出現,可能被整合在某一芯片中。
1.2 掃描碼 ,Make Code ,Break Code ,Typematic
當鍵盤上有鍵被按下,松開,按住,鍵盤將產生掃描碼( Scan Code ),這些掃描碼將被 i8048 直接得到。掃描碼有兩種,Make Code 和 Break Code。當一個鍵被按下或按住時產生的是 Make Code ,當一個鍵被松開產生的是 Break Code。每個鍵被分配了唯一的 Make Code 和 Break Code ,這樣主機通過掃描碼就可以知道是哪一個鍵。簡單的說就是按下鍵,產生一個 Make Code。松開鍵,產生一個 Break Code。
而對於按住不放的情況呢。我們可以打開一個記事本,把 'a' 鍵按住不放,可以看到會不停的產生 'a' 直到我們松開。這是由於,當按住一個鍵不放時,將會 Typematic ,也就是自動打。每隔一定時間,自動產生一個被按住的鍵的 Make Code,直到最后松開該鍵。對於 Typematic 有兩個重要的參數,一個是 Typematic Delay ,決定了按下多長時間之后,進入 Typematic,另一個是 Typematic Rate,決定了在進入 Typematic 之后,一秒鍾內能產生多少個 Make Code 。現在我們再打開記事本,按住 'a' 不放,仔細觀察,將看到第一個 'a' 和第二個 'a' 之間間隔的時間明顯比其他的要長,而之后每個'a'之間的時間間隔是一樣的。
而對於同時按下多個鍵的情況呢。在一個鍵被按下,產生了 Make Code,而沒有被松開,沒有產生 Break Code 的時候,再按下另一個鍵,於是產生了另一個鍵的 Make Code ,就算是這兩個鍵被同時按下。之后,這兩個鍵松開時,各自產生各自的 Break Code。更多的鍵的情況也是一樣。比如要按 Ctrl 和 A,下面的情況就算作是同時按下了 Ctrl 和 A。按Ctrl,產生 Ctrl 的 Make Code,然后按A,產生 A 的 Make Code,然后各自松開,各自產生各自的 Break Code。
而對於按一個鍵不放的時候,再按另一個鍵的情況呢。我們可以打開一個記事本,把'a'鍵按住不放,不要松開,然后再按's'鍵不放。我們可以看到當按下's'時,'a'鍵並沒有松,但是並沒有'a'再出現了,而是's'開始出現,這時即使松開了's','a'也不會繼續出現了。
1.3 掃描碼集
到目前為止一共有三套掃描碼集( Scan Code Set ),ps/2 鍵盤默認使用第二套。不過可以設置 i8042,讓 i8042 把得到的 Scan Code 翻譯成 Scan Code Set 1 中的 Scan Code ,這樣鍵盤驅動從 i8042 得到的所有 Scan Code 都是第一套中的 Scan Code(實際中驅動也是這么做的)。所以我們只討論 Scan Code Set 1 。需要說明的是 Scan Code 和 ASCII碼完全不相同。
在 Scan Code Set 1 中,大多數鍵的 Make Code,Break Code 都是一個字節。他們的 Make Code 的最高位都為0,也就是他們的 Make Code 都小於 0x7F。而他們的 Break Code 為其 Make Code 或運算 80h ,也就是把 Make Code 的低7位不變,最高位設置為1。
還有一些擴展按鍵,他們的 Scan Code 是雙字節的。他們的第一個字節都是E0h,表明這是一個擴展鍵。第2個字節,和單字節 Scan Code 的情況相同。
還有一個特殊的鍵,Pause/Break 鍵,它的 Make Code 為 E1,1D,45 E1,9D,C5,注意是 E1h 開頭。而且它沒有 Break Code 。
我們按鍵的 Make Code 的值的大小,列出 Scan Code Set 1 中的所有掃描碼
KEY MAKE BREAK
ESC 01 81
1 02 82
2 03 83
3 04 84
4 05 85
5 06 86
6 07 87
7 08 88
8 09 89
9 0A 8A
0 0B 8B
- 0C 8C
= 0D 8D
BKSP 0E 8E
TAB 0F 8F
Q 10 19
W 11 91
E 12 92
R 13 93
T 14 94
Y 15 95
U 16 96
I 17 97
O 18 98
P 19 99
[ 1A 9A
] 1B 9B
ENTER 1C 9C
L_CTRL 1D 9D
A 1E 9E
S 1F 9F
D 20 A0
F 21 A1
G 22 A2
H 23 A3
J 24 A4
K 25 A5
L 26 A6
; 27 A7
' 28 A8
` 29 89
L_SHFT 2A AA
2B AB
Z 2C AC
X 2D AD
C 2E AE
V 2F AF
B 30 B0
N 31 B1
M 32 B2
, 33 B3
. 34 B4
/ 35 B5
R_SHFT 36 B6
KP * 37 B7
L_ALT 38 B8
SPACE 39 B9
CAPS 3A BA
F1 3B BB
F2 3C BC
F3 3D BD
F4 3E BE
F5 3F BF
F6 40 C0
F7 41 C1
F8 42 C2
F9 43 C3
F10 44 C4
NUM 45 C5
SCROLL 46 C6
KP 7 47 C7
KP 8 48 C8
KP 9 49 C9
KP - 4A CA
KP 4 4B CB
KP 5 4C CC
KP 6 4D CD
KP + 4E CE
KP 1 4F CF
KP 2 50 D0
KP 3 51 D1
KP 0 52 D2
KP . 53 D3
F11 57 D7
F12 58 D8
KP EN E0,1C E0,9C
R_CTRL E0,1D E0,9D
KP / E0,35 E0,B5
R_ALT E0,38 E0,B8
HOME E0,47 E0,C7
UP ARROW E0,48 E0,C8
PG UP E0,49 E0,C9
L ARROW E0,4B E0,CB
R ARROW E0,4D E0,CD
END E0,4F E0,CF
D ARROW E0,50 E0,D0
PG DN E0,51 E0,D1
INSERT E0,52 E0,D2
DELETE E0,53 E0,D3
L GUI E0,5B E0,DB
R GUI E0,5C E0,DC
APPS E0,5D E0,DD
PRNT SCRN E0,2A, E0,37 E0,B7, E0,AA
PAUSE E1,1D,45 E1,9D,C5 -NONE
這里說幾句對驅動沒有幫助的題外話,記不清是由於先有了關於 Scan Code 的值的猜測,才去按這個順序列 Scan Code ,還是先這樣列 Scan Code ,才有了關於 Scan Code 的值的猜測。總之,用這個 Make Code 的順序,和我們現在鍵盤上鍵的布局做對照,我們大致就能猜到為什么 A 鍵的 Make Code 值為 0x1e,為什么 H 鍵的 Make Code 值為 0x23。我們拿其中的一小段舉例子,A 1E,S 1F,D 20,F 21,G 22,H 23,看看鍵盤上 A,S,D,F,G,H 的位置吧。能感覺到些什么吧,感覺不到就算了,這個和驅動是無關的。從 Scan Code Set 1,可能還能推測出來最早的鍵盤的樣子。以及發生在鍵盤上的一些變化。我們注意到 F10 和 F11,F12 的 Make Code 不是連在一起的,估計比較早的鍵盤只有10個功能鍵,而不是現在的12個功能鍵。從鍵的 Make Code 來看,有可能曾經使用的一些鍵,現在已經不出現在鍵盤上了。
還有一個值得注意的是,如果有 Make Code 為 0x60 的鍵,那么它的 Break Code 應該為 0x60+0x80=0xE0。那么這個鍵的 Break Code 將會和 表示擴展碼的 0xE0 搞混。不過還好,並沒有 Make Code 為 0x60 的鍵,所以不會發生搞混的情況。 i8042 鍵盤控制器
鍵盤驅動直接讀寫 i8042 芯片,通過 i8042 間接的向鍵盤中的 i8048 發命令。所以對於驅動來說,直接發生聯系的只有 i8042 ,因此我們只介紹 i8042 ,不介紹 i8048。
象 i8042,i8048 這樣的芯片,本身就是一個小的處理器,它的內部有自己的處理器,有自己的 Ram,有自己的寄存器,等等。
i8042 有 4 個 8 bits 的寄存器,他們是 Status Register(狀態寄存器),Output Buffer(輸出緩沖器),Input Buffer(輸入緩沖器),Control Register(控制寄存器)。使用兩個 IO 端口,60h 和 64h。
Status Register(狀態寄存器)
狀態寄存器是一個8位只讀寄存器,任何時刻均可被cpu讀取。其各位定義如下
Bit7: PARITY-EVEN(P_E): 從鍵盤獲得的數據奇偶校驗錯誤
Bit6: RCV-TMOUT(R_T): 接收超時,置1
Bit5: TRANS_TMOUT(T_T): 發送超時,置1
Bit4: KYBD_INH(K_I): 為1,鍵盤沒有被禁止。為0,鍵盤被禁止。
Bit3: CMD_DATA(C_D): 為1,輸入緩沖器中的內容為命令,為0,輸入緩沖器中的內容為數據。
Bit2: SYS_FLAG(S_F): 系統標志,加電啟動置0,自檢通過后置1
Bit1: INPUT_BUF_FULL(I_B_F): 輸入緩沖器滿置1,i8042 取走后置0
BitO: OUT_BUF_FULL(O_B_F): 輸出緩沖器滿置1,CPU讀取后置0
Output Buffer(輸出緩沖器)
輸出緩沖器是一個8位只讀寄存器。驅動從這個寄存器中讀取數據。這些數據包括,掃描碼,發往 i8042 命令的響應,間接的發往 i8048 命令的響應。
Input Buffer(輸入緩沖器)
輸入緩沖器是一個8位只寫寄存器。緩沖驅動發來的內容。這些內容包括,發往 i8042 的命令,通過 i8042 間接發往 i8048 的命令,以及作為命令參數的數據。
Control Register(控制寄存器)
也被稱作 Controller Command Byte (控制器命令字節)。其各位定義如下
Bit7: 保留,應該為0
Bit6: 將第二套掃描碼翻譯為第一套
Bit5: 置1,禁止鼠標
Bit4: 置1,禁止鍵盤
Bit3: 置1,忽略狀態寄存器中的 Bit4
Bit2: 設置狀態寄存器中的 Bit2
Bit1: 置1,enable 鼠標中斷
BitO: 置1,enable 鍵盤中斷
2個端口 0x60,0x64
驅動中把 0x60 叫數據端口
驅動中把 0x64 叫命令端口
1.5 命令
驅動可以直接給 i8042 發命令,可以通過 i8042 間接給 i8048 發命令。命令這部分內容直接來自 < 參考資料 [1] >。
1.5.1 發給i8042的命令
驅動對鍵盤控制器發送命令是通過寫端口64h實現的,共有12條命令,分別為
20h
准備讀取8042芯片的Command Byte;其行為是將當前8042 Command Byte的內容放置於Output Register中,下一個從60H端口的讀操作將會將其讀取出來。
60h
准備寫入8042芯片的Command Byte;下一個通過60h寫入的字節將會被放入Command Byte。
A4h
測試一下鍵盤密碼是否被設置;測試結果放置在Output Register,然后可以通過60h讀取出來。測試結果可以有兩種值:FAh=密碼被設置;F1h=沒有密碼。
A5h
設置鍵盤密碼。其結果被按照順序通過60h端口一個一個被放置在Input Register中。密碼的最后是一個空字節(內容為0)。
A6h
讓密碼生效。在發布這個命令之前,必須首先使用A5h命令設置密碼。
AAh
自檢。診斷結果放置在Output Register中,可以通過60h讀取。55h=OK。
ADh
禁止鍵盤接口。Command Byte的bit-4被設置。當此命令被發布后,Keyboard將被禁止發送數據到Output Register。
AEh
打開鍵盤接口。Command Byte的bit-4被清除。當此命令被發布后,Keyboard將被允許發送數據到Output Register。
C0h
准備讀取Input Port。Input Port的內容被放置於Output Register中,隨后可以通過60h端口讀取。
D0h
准備讀取Outport端口。結果被放在Output Register中,隨后通過60h端口讀取出來。
D1h
准備寫Output端口。隨后通過60h端口寫入的字節,會被放置在Output Port中。
D2h
准備寫數據到Output Register中。隨后通過60h寫入到Input Register的字節會被放入到Output Register中,此功能被用來模擬來自於Keyboard發送的數據。如果中斷被允許,則會觸發一個中斷。
1.5.2 發給8048的命令
共有10條命令,分別為
EDh
設置LED。Keyboard收到此命令后,一個LED設置會話開始。Keyboard首先回復一個ACK(FAh),然后等待從60h端口寫入的LED設置字節,如果等到一個,則再次回復一個ACK,然后根據此字節設置LED。然后接着等待。。。直到等到一個非LED設置字節(高位被設置),此時LED設置會話結束。
EEh
診斷Echo。此命令純粹為了檢測Keyboard是否正常,如果正常,當Keyboard收到此命令后,將會回復一個EEh字節。
F0h
選擇Scan code set。Keyboard系統共可能有3個Scan code set。當Keyboard收到此命令后,將回復一個ACK,然后等待一個來自於60h端口的Scan code set代碼。系統必須在此命令之后發送給Keyboard一個Scan code set代碼。當Keyboard收到此代碼后,將再次回復一個ACK,然后將Scan code set設置為收到的Scan code set代碼所要求的。
F2h
讀取Keyboard ID。由於8042芯片后不僅僅能夠接Keyboard。此命令是為了讀取8042后所接的設備ID。設備ID為2個字節,Keyboard ID為83ABh。當鍵盤收到此命令后,會首先回復一個ACK,然后,將2字節的Keyboard ID一個一個回復回去。
F3h
設置Typematic Rate/Delay。當Keyboard收到此命令后,將回復一個ACK。然后等待來自於60h的設置字節。一旦收到,將回復一個ACK,然后將Keyboard Rate/Delay設置為相應的值。
F4h
清理鍵盤的Output Buffer。一旦Keyboard收到此命令,將會將Output buffer清空,然后回復一個ACK。然后繼續接受Keyboard的擊鍵。
F5h
設置默認狀態(w/Disable)。一旦Keyboard收到此命令,將會將Keyboard完全初始化成默認狀態。之前所有對它的設置都將失效--Output buffer被清空,Typematic Rate/Delay被設置成默認值。然后回復一個ACK,接着等待下一個命令。需要注意的是,這個命令被執行后,鍵盤的擊鍵接受是禁止的。如果想讓鍵盤接受擊鍵輸入,必須Enable Keyboard。
F6h
設置默認狀態。和F5命令唯一不同的是,當此命令被執行之后,鍵盤的擊鍵接收是允許的。
FEh
Resend。如果Keyboard收到此命令,則必須將剛才發送到8042 Output Register中的數據重新發送一遍。當系統檢測到一個來自於Keyboard的錯誤之后,可以使用自命令讓Keyboard重新發送剛才發送的字節。
FFh
Reset Keyboard。如果Keyboard收到此命令,則首先回復一個ACK,然后啟動自身的Reset程序,並進行自身基本正確性檢測(BAT-Basic Assurance Test)。等這一切結束之后,將返回給系統一個單字節的結束碼(AAh=Success, FCh=Failed),並將鍵盤的Scan code set設置為2。
1.5.3 讀到的數據
00h/FFh
當擊鍵或釋放鍵時檢測到錯誤時,則在Output Bufer后放入此字節,如果Output Buffer已滿,則會將Output Buffer的最后一個字節替代為此字節。使用Scan code set 1時使用00h,Scan code 2和Scan Code 3使用FFh。
AAh
BAT完成代碼。如果鍵盤檢測成功,則會將此字節發送到8042 Output Register中。
EEh
Echo響應。Keyboard使用EEh響應從60h發來的Echo請求。
F0h
在Scan code set 2和Scan code set 3中,被用作Break Code的前綴。
FAh
ACK。當Keyboard任何時候收到一個來自於60h端口的合法命令或合法數據之后,都回復一個FAh。
FCh
BAT失敗代碼。如果鍵盤檢測失敗,則會將此字節發送到8042 Output Register中。
FEh
Resend。當Keyboard任何時候收到一個來自於60h端口的非法命令或非法數據之后,或者數據的奇偶交驗錯誤,都回復一個FEh,要求系統重新發送相關命令或數據。
83ABh
當鍵盤收到一個來自於60h的F2h命令之后,會依次回復83h,ABh。83AB是鍵盤的ID。
Scan code
除了上述那些特殊字節以外,剩下的都是Scan code。
1.6 端口操作
首先介紹一下端口的讀寫操作,驅動中使用函數 READ_PORT_UCHAR 進行讀操作,READ_PORT_UCHAR 中使用CPU讀端口指令,in。驅動中使用函數 WRITE_PORT_UCHAR 進行寫操作,WRITE_PORT_UCHAR 中使用CPU寫端口指令,out。
1.6.1 讀取狀態寄存器
讀取狀態寄存器的方法,對64h端口進行讀操作。
1.6.2 讀數據
需要讀取的數據有,i8042從i8048得到的按鍵的掃描碼,i8042命令的ACK,i8042從i8048得到的i8048命令的ACK,需要命令重發的RESEND,一些需要返回結果的命令得到的結果。
當有數據需要被驅動讀走的時候,數據被放入輸出緩沖器,同時將狀態寄存器的bit0(OUTPUT_BUFFER_FULL)置1,引發鍵盤中斷(鍵盤中斷的IRQ為1)。由於鍵盤中斷,引起由鍵盤驅動提供的鍵盤中斷服務例程被執行。在鍵盤中斷服務例程中,驅動會從i8042讀走數據。一旦數據讀取完成,狀態寄存器的bit0會被清0。
讀數據的方法,首先,讀取狀態寄存器,判斷bit0,狀態寄存器bit0為1,說明輸出緩沖器中有數據。保證狀態寄存器bit0為1,然后對60h端口進行讀操作,讀取數據。
這里我們要談一點很有用的題外話,前面提到的 IRQ,是 Interrupt Request line,中斷請求線,是一個硬件線,它和中斷向量是不同的。中斷向量是用來在中斷描述符表(IDT)中查找中斷服務例程的那個序號。鍵盤的 IRQ 是1,鍵盤中斷服務例程對應的中斷向量可不是1。這點要弄清楚。
1.6.3 向i8042發命令
當命令被發往i8042的時候,命令被放入輸入緩沖器,同時引起狀態寄存器的 Bit1 置1,表示輸入緩沖器滿,同時引起狀態寄存器的 Bit2 置1,表示寫入輸入緩沖器的是一個命令。
向i8042發命令的方法,首先,讀取狀態寄存器,判斷bit1,狀態寄存器bit1為0,說明輸入緩沖器為空,可以寫入。保證狀態寄存器bit1為0,然后對64h端口進行寫操作,寫入命令。
1.6.4 間接向i8048發命令
向i8042發這些命令,i8042會轉發i8048,命令被放入輸入緩沖器,同時引起狀態寄存器的Bit1 置1,表示輸入緩沖器滿,同時引起狀態寄存器的 Bit2 置1,表示寫入輸入緩沖器的是一個命令。這里我們要注意,向i8048發命令,是通過寫60h端口,而后面發命令的參數,也是寫60h端口。i8042如何判斷輸入緩沖器中的內容是命令還是參數呢,我們在后面發命令的參數中一起討論。
向i8048發命令的方法,首先,讀取狀態寄存器,判斷bit1,狀態寄存器bit1為0,說明輸入緩沖器為空,可以寫入。保證狀態寄存器bit1為0,然后對60h端口進行寫操作,寫入命令。(注意呢!)
1.6.5 發命令的參數
某些命令需要參數,我們在發送命令之后,發送它的參數,參數被放入輸入緩沖器,同時引起狀態寄存器的Bit1 置1,表示輸入緩沖器滿。這里我們要注意,向i8048發命令,是通過寫60h端口,發命令的參數,也是寫60h端口。i8042如何判斷輸入緩沖器中的內容是命令還是參數呢。i8042是這樣判斷的,如果當前狀態寄存器的Bit3 為1,表示之前已經寫入了一個命令,那么現在通過寫60h端口放入輸入緩沖器中的內容,就被當做之前命令的參數,並且引起狀態寄存器的 Bit3 置0。如果當前狀態寄存器的 Bit3 為0,表示之前沒有寫入命令,那么現在通過寫60h端口放入輸入緩沖器中的內容,就被當做一個間接發往i8048的命令,並且引起狀態寄存器的 Bit3 置1。
向i8048發參數的方法,首先,讀取狀態寄存器,判斷bit1,狀態寄存器bit1為0,說明輸入緩沖器為空,可以寫入。保證狀態寄存器bit1為0,然后對60h端口進行寫操作,寫入參數。(同上面紅字所說一樣)
"1 ps/2 鍵盤的硬件" 主要參考下面的資料,關於 ps/2 鍵盤硬件更多的內容也請參考下面的資料
[1] http://pagoda-ooos.51.net/os_book/driver/driver-ke yboard_2.htm (中文)
[2] http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/ PS2.pdf (中文)
[3] http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/ (英文)
下面是我的代碼,用來每隔一秒就虛擬一個ctrl+a的操作。
// HDkey.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include "windows.h"
#include "winio.h"//程序要運行時要包含winio.dll和winio.sys(NT系統下);winio.dll和winio.vxd(98系統下);
#include "time.h"
#define key_cmd 0x64//鍵盤命令端口
#define key_dat 0x60//鍵盤數據端口
//等待緩沖區為空
void KBCwait4IBE() {
DWORD ch=0; //注意,在這循環,我並沒有改變鍵盤命令端口的數據,但也不會出現死循環,/ 因為鍵盤中斷會在中間把數據讀取並置零,那就不會導致死循環了
do { GetPortVal(key_cmd,&ch,1);//讀取鍵盤的命令端口,得出ch
} while(ch & 0x2);//bit1是1的話,說明輸入緩沖器已滿,反復檢測!直到空
}
//鍵 按下
void MakeKeyDown(DWORD VirtualKey) {
DWORD K_Make_Code=MapVirtualKey(VirtualKey,0);//winuser. h里面定義好的函數!
KBCwait4IBE();
SetPortVal(key_cmd,0xd2,1);//d2是說,准備寫數據到Output Register中。隨后通過60h寫入到Input Register的字節會被放入到Output Register中,此功能被用來模擬來自於Keyboard發送的數據。如果中斷被允許,則會觸發一個中斷處理。(具體看原理文檔) SetPortVal(key_dat,K_Make_Code,1); } //鍵 放開 void MakeKeyUp(DWORD VirtualKey) { DWORD K_Make_Code=MapVirtualKey(VirtualKey,0);//鍵的掃描碼 DWORD K_Break_Code=K_Make_Code+0x80;//鍵的斷碼 KBCwait4IBE(); SetPortVal(key_cmd,0xd2,1);//d2是說,准備寫數據到Output Register中。隨后通過60h寫入到Input Register的字節會被放入到Output Register中,此功能被用來模擬來自於Keyboard發送的數據。如果中斷被允許,則會觸發一個中斷處理。(具體看原理文檔) SetPortVal(key_dat,K_Break_Code,1); } void main() { bool Intial; Intial=InitializeWinIo(); while(1) { MakeKeyDown(VK_CONTROL); MakeKeyDown('A'); MakeKeyUp('A'); MakeKeyUp(VK_CONTROL); Sleep(1000); } ShutdownWinIo(); }
其實也挺簡單的,最難還是在於理解鍵盤是怎樣產生一個按鍵消息的原理,要非常耐心地看!和多參考別人的實現方法!
這個WinIo很牛B啊!如果用來做外掛應該很爽!