|
The Ancient Art Of Training
Or What The Mega Trainer Gods Stuff Is All About
|
Not Assigned
|
04 June 1999
| by
A Nameless Stranger
|
|
|
Courtesy of Fravia's page of
reverse engineering
|
slightly edited
by fravia+
|
fra_00xx 980604 anamstra 1100 NA PC
|
I feel some sadness and disappointment in this contribution (see the drink/imposing bit :-) Yet it is indeed
a valid contribution. The Author wrote to me
the following:
lately my students requested a little excursion to the
mystified world of trainers.
This little essay is the result of the lesson I held
about this topic.
My students most happily refer to your site and
teachings.
So it might be fair to give your students the
possibility to refer to my work.
Yours sincerely,
a nameless stranger
and the author of this essay is right: the strainer world is a very interesting reversing world, and I invite
all readers to have a (deep) look at it. There are sites on the web that have perfectioned the
softice techniques we use everyday ONLY with the purpose of modifying games. Make a search for +softice +doom
(or quake, or whatever) and you'll see what I mean.
Yeah, of course "pure" crackers and reversers are not alone:
there are all kinds of reversers out there in the great great Web. Some of these
'revkinds' are quite alien for us, some are very near, some touch
'our interests' only marginally, but many others have (quite) something to teach us,
like the demomakers, for instance, and also the trainers' experts like "A
nameless
stranger", here and now.
"This is the MINIMUM trainer (i.e. a hotkey and memory
patching)! Just a starting point."
Indeed! Awaiting much MORE! :-)
| |
|
Outside they call them gods
Magicians with mighty rods
Inside it's just little work
Can be done by every jerk.
| |
Rating
|
( )Beginner (x)Intermediate ( )Advanced ( )Expert
| |
All in all this is something every beginner should be able to understand and reproduce.
It's rated intermediate 'cause it's assumed that the reader knows basic data types, a bit assembly,
a bit C and basic reversing techniques. A little knowledge of programming with Win32 API should
be helpful.
The Ancient Art Of Training
Or What The Mega Trainer Gods Stuff Is All About
Written by
A Nameless Stranger
This essay covers the concept of trainers in Windows9x environments in FAQ style.
If the reader needs a ready made trainer 'skeleton' this is definitely the wrong essay,
but the author covers all topics the reader needs to know for coding his/her own trainer.
This way the reader will have the possibility to be proud of his/her own work and he/she
won't have to lie about the credits.
SoftIce
GameHack
C compiler
The reader doesn't need any special drink and/or music. The author won't impose his/her preferences on the reader.
Trainer targets are usually games which can be found at any game store or any major distro site.
The reader might have already one or two games at home. The author mostly used Expendable in the examples.
A very brief history of trainers:
Once upon a time cracker groups didn't only crack. They coded their own intros, demos, trainers...
As long as there were such cracker groups trainers were coded.
Later the cracker groups stopped coding and specialized in changing jne/je to nop.
A few individuals conquered the free space and tried to build something like a trainer scene.
Today the warez groups try to revive the glory of the old days by producing intros, demos and
trainers again.
What's a trainer?
A trainer is a program that allows to manipulate certain values of games such as
life points, experience points, ammo, credits, tiberium, gold, armor, .... at run time.
In short words - A trainer lets the player cheat.
What's a 'mega' trainer?
A mega trainer is a trainer which allows cheating on multiple values.
It not only freezes life points. It freezes time, ammo, life points, credits, skips levels,
adds weapons or whatever.
What does 'freezing' mean?
'Freezing' is the effect a player experiences when a trainer stops values from changing.
This can be accomplished by modifying the game's code in a way that the value won't be accessed
anymore or by updating the value in short intervals. Another way to 'freeze' values is to
change the code which accesses the value.
Example:
If the code that drains your life points looks like this:
'sub [esi+1C6FCF6],ax' change it to 'mov [esi+1C6FCF6],64'
and you'll always have 100 LP.
How does a trainer work?
In most cases a trainer reacts upon defined key strokes (hotkeys).
If the right key is pressed the trainer's 'engine' attemps to patch the memory
of the target's process at the address where the desired value/code is located.
How do I patch the memory of a target's process?
This is no secret. A lot of material can be found on this topic in various books, magazines, journals...
The main idea is to utilize the Win32 API function WriteProcessMemory().
The Win32 API reference sates:
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress, // address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data from
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten // actual number of bytes written
);
How do I find out all these parameters?
1. parameter: handle to process whose memory is written to
A handle to a process can be obtained by calling the functions FindWindow(),
GetWindowThreadProcessId() and OpenProcess() like this:
HANDLE hWndTarget = FindWindow( NULL, "Your target's window name");
DWORD dwProcessId;
GetWindowThreadProcessId(hWndTarget, &dwProcessId);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
If you're unsure about the target's window name use any of those nice little utilities which
list the windows' names.
Remark:
If you're using OpenProcess() you should use CloseHandle() too else you will produce
a nice little memory leak. Closing a process' handle does NOT cause closing of the associated
process!
For a detailed description of the above functions consult the 'Appendix - Win32 functions used'.
2. parameter: address to start writing to
To find the address where you have to patch the target's memory is the trickiest part of it all.
But there's help. The easiest way to find the right address is to use GameHack 2.0 (www.gamehack.com).
This little program allows the user to scan a traget's memory for specified values and shows the
associated addresses. When the desired value changes the user lets GameHack scan the found
addresses for the new value. This way GameHack sorts out the wrong addresses until there are only
a few left.
Get it. Take a look at it. For composing trainers it's a very valuable tool. Of course you can
write such a memory scanner on your own and add some advanced pattern scan methods.
It's not very difficult if you've coded your own trainer you're already half the way down.
Creating the UI is the worst of it all.
Anyway, use GameHack to find the address where your target stores the desired value.
Sounds easy, doesn't it? Well, it is, but you may experience some problems relying only on
GameHack's addresses. The problem is that you can't take for granted that the desired values
are always stored at the same addresses.
Example:
You can use addresses found with GameHack to train e.g. Heroes of Might & Magic 3 which works
fine in a single scenario or in the first campain but later, in the next campain or in multi
player mode the memory layout changes i.e. the addresses of the values change and become useless.
You see it's necessary to check if your addresses work all the time not only the first five
minutes.
If the address(es) change it's a good idea not to patch the value but the code that accesses the
value. The address of the code can be easily located by putting a memory breakpoint on the value's
address. (i.e. 'bpm address w' or 'bpr addr1 addr2 w' in SoftIce).
Example:
You've found with GameHack that your target stores life points in a WORD at address 0x1C6FCF6.
Start your target.
Set a breakpoint 'bpmw 1C6FCF6 w' in softice.
Resume your target.
When your life points change SoftIce pops up at a location like this:
:004657FA 668986F6FCC601 mov word ptr [esi+01C6FCF6], ax
This line of code changes the value of your life points. To stop the program killing you
simply nop it out by writing 7 times 90 (nop) to address 4657FA. If you want to disable
your trainer restore the original bytes at address 4657FA.
Of course there some other ways to locate the right address - scanning for specifc patterns
or even dead listing - but I think you got the idea, didn't you?
3. parameter: pointer to buffer to write data from
A buffer with data to write from looks like this:
BYTE bNewData[]={0x90,0x90,0x90,0x90,0x90,0x90,0x90};
Yes, that's really all. Simply create an array holding the bytes you want to write. Pay
attention to the Intel byte order!
4. parameter: number of bytes to write
Count your bytes or let the function sizeof() count them for you:
DWORD dwNewDataSize = sizeof(bNewData);
5. parameter: actual number of bytes written
We don't want to know. Set it to NULL.
The complete trainer 'engine' should look like this:
BYTE bNewData[]={0x90,0x90,0x90,0x90,0x90,0x90,0x90};
DWORD dwNewDataSize = sizeof(bNewData);
HANDLE hWndTarget = FindWindow( NULL, "Expendable");
DWORD dwProcessId;
GetWindowThreadProcessId(hWndTarget, &dwProcessId);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
WriteProcessMemory(hProcess, 0x4657FA, &bNewData, dwNewDataSize, NULL);
CloseHandle(hProcess);
Yes, it's that simple. You've already trained Expendable's life points.
See 'Appendix - a live record' for a brief description of the whole process.
How do I realize hotkeys?
Using the Win32 API you can realize hotkeys in various ways.
1. You can use RegisterHotkey(),
but you shouldn't 'cause it doesn't work reliable in every case.
2. You can use SetWindowsHook().
It works reliable most times but you have to code a DLL to use it for trainer purposes.
If another process starts after your trainer wich uses SetWindowsHook() too and
doesn't call CallNextHook() your hotkey will be disabled. See the Win32 API reference
for details.
3. You can use GetAsyncKeyState().
The Win32 API reference states:
The GetAsyncKeyState function determines whether a key is up or down at the time the function is called, and whether the key was pressed after a previous call to GetAsyncKeyState.
SHORT GetAsyncKeyState(
int vKey // virtual-key code
);
Parameters:
vKey - Specifies one of 256 possible virtual-key codes.
See 'Appendix - All the virtual keys with their values'.
Return Values:
If the function succeeds, the return value specifies whether the key was pressed since the
last call to GetAsyncKeyState, and whether the key is currently up or down....
To use this function for hotkeys the only thing you have to do is polling.
Polling means to let it run in a loop.
If you call it in a loop like:
while(1)
{
if (GetAsyncKeyState(VK_F12)) // if F12 is down or was down since the last call
{
train(); // call the trainer engine
}
};
it will consume too much if not all of the cpu time. So it is important to time the 'loop'
carefully.
A timed loop is nothing else than a timer callback function. You can realize it like this:
void CALLBACK PollKeys (HWND hWnd,UINT uMsg,UINT idEvent,DWORD dwTime)
{
if (GetAsyncKeyState(VK_F12)) // if F12 is down or was down since the last call
{
train(); // call the trainer engine
}
}
Now you need to activate the timer with:
SetTimer(hWnd, 666, 1000, (TIMERPROC) PollKeys);
Parameters:
hWnd = your trainer's window handle (HANDLE)
666 = a unique identifier (UINT)
1000 = timer interval in ms (UINT)
PollKeys = address of timer procedure (TIMERPROC)
Generally it's a good idea to check the game's key layout before choosing your hotkey i.e.
don't use F5 if the game uses this for e.g. quicksave.
Remark:
The timer callback for the hotkeys consumes cpu time everytime it's called.
You shouldn't set the timer interval to very short periods (<75 ms) to avoid slow downs
of the running target. Usually a ~1000 ms interval is an acceptable choice.
See the 'Appendix - Win32 functions used' for detailed information.
That's all?
Yes! This is all you need to know for coding trainers. You know how find the right addresses,
how to patch memory and how to realize hotkeys. You may need to work out the address finding
part a bit but with GameHack on your side you'll soon discover how easy things really are.
All what's left to do is assemble what you've learned to a working Win32 program and ready is
your trainer. You've mastered your way to the olympus of the 'trainer gods'.
Remark:
The author tried to make this as short and simple as possible. This is the
MINIMUM trainer (i.e. a hotkey and memory patching)! Just a starting point. If you think
your trainer won't work this way 'cause you need an in built memory scanner or the use
of the debug interface or whatever, good, very good: CODE IT YOURSELF!
Appendix - a live record
This is just an EXAMPLE for illustration purposes. If you think that some code locations
should be patched another way, good, do so. You should of course add some sanity checking
i.e. error handling to your trainer code!
- Chosen target: Expendable
- Used GameHack to find the adressses: 04A0134 - time (WORD)
1C6FCF6 - life points (WORD)
1C6FCxx - various ammo (WORD)
- Used 'bpmw 04A0134 w' in Softice to track down the code that modifies
the time value.
- Found code that counts down the time value at:
0041C63E 48 dec eax
- Wrote down the address and the original byte 48.
Byte that eliminates the count down: 90 - nop.
- Used 'bpmw 1C6FCF6 w' in Softice to track down the code that modifies
the life points.
- Found code that counts down the life points at:
004657FA 668986F6FCC601 mov word ptr [esi+01C6FCF6], ax
- Wrote down the address and the original bytes 66 89 86 F6 FC C6 01.
Bytes that 'freeze' the life points 7 x 90 - nop.
- Used 'bpmw 1C6FCxx w' on the various ammo values.
- Found and eliminated all code that counts down ammo values at:
0046BB0E 2B44241C sub eax, dword ptr [esp+1C]
Bytes that eliminate the ammo count down: 4 x 90 - nop.
- Testing showed that grenades aren't handled by the above ammo
count down.
- Used bpmw .... to track down the grenade count down code.
- Found code that counts down grenades at:
0041F53C 48 dec eax
Byte that eliminates that code 90 - nop.
- Used GameHack to get Expendable's window name: 'Expendable'
- Set up the trainer engine to open Expendable's process:
HANDLE hWndTarget = FindWindow( NULL, "Expandable");
DWORD dwProcessId;
GetWindowThreadProcessId(hWndTarget, &dwProcessId);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
- Set up the trainer code to handle 3 hotkeys (time, life, ammo):
void CALLBACK PollKeys (HWND hWnd,UINT uMsg,UINT idEvent,DWORD dwTime)
{
if (GetAsyncKeyState(VK_F10))
}
TrainTime();
}
if (GetAsyncKeyState(VK_F11))
{
TrainLife();
}
if (GetAsyncKeyState(VK_F12))
{
TrainAmmo();
}
}
- Set up the trainer's engine like this:
BYTE bTimeDataNew = 0x90; // the new code to patch
BYTE bTimeDataOld = 0x48; // the old code to restore
BYTE temp = 0; // guess
void TrainTime()
{
WriteProcessMemory(hProcess, 41C63E, &bTimeDataNew, 1, NULL); // patch the memory
BYTE temp = bTimeDataNew; // swap bTimeDataNew
bTimeDataNew = bTimeDataOld; // with bTimeDataOld
bTimeDataOld = temp; // to make sure the next time the hotkey
// is pressed the trainer switches correct on/off
}
- Did the same with ammo and life points.
- Compiled. Tested. Ready.
Appendix - Win32 functions used
WriteProcessMemory
The WriteProcessMemory function writes memory in a specified process.
The entire area to be written to must be accessible, or the operation fails.
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress, // address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data to
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten // actual number of bytes written
);
Parameters
hProcess
Identifies an open handle to a process whose memory is to be written to.
The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access to the process.
lpBaseAddress
Points to the base address in the specified process to be written to.
Before any data transfer occurs, the system verifies that all data in the base address
and memory of the specified size is accessible for write access.
If this is the case, the function proceeds; otherwise, the function fails.
lpBuffer
Points to the buffer that supplies data to be written into the address space of the
specified process.
nSize
Specifies the requested number of bytes to write into the specified process.
lpNumberOfBytesWritten
Points to the actual number of bytes transferred into the specified process.
This parameter is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored.
Return Values
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero.
To get extended error information, call GetLastError.
The function will fail if the requested write operation crosses into an area of the process
that is inaccessible.
Remarks
WriteProcessMemory copies the data from the specified buffer in the current process to the
address range of the specified process. Any process that has a handle with PROCESS_VM_WRITE
and PROCESS_VM_OPERATION access to the process to be written to can call the function.
The process whose address space is being written to is typically, but not necessarily, being
debugged. The entire area to be written to must be accessible. If it is not, the function
fails as noted previously.
FindWindow
The FindWindow function retrieves the handle to the top-level window whose class name and
window name match the specified strings. This function does not search child windows.
HWND FindWindow(
LPCTSTR lpClassName, // pointer to class name
LPCTSTR lpWindowName // pointer to window name
);
Parameters
lpClassName
Points to a null-terminated string that specifies the class name or is an atom that
identifies the class-name string. If this parameter is an atom, it must be a global atom
created by a previous call to the GlobalAddAtom function. The atom, a 16-bit value, must
be placed in the low-order word of lpClassName; the high-order word must be zero.
lpWindowName
Points to a null-terminated string that specifies the window name (the window's title).
If this parameter is NULL, all window names match.
Return Values
If the function succeeds, the return value is the handle to the window that has the
specified class name and window name. If the function fails, the return value is NULL.
To get extended error information, call GetLastError.
GetWindowThreadProcessId
The GetWindowThreadProcessId function retrieves the identifier of the thread that created
the specified window and, optionally, the identifier of the process that created the window.
This function supersedes the GetWindowTask function.
DWORD GetWindowThreadProcessId(
HWND hWnd, // handle of window
LPDWORD lpdwProcessId // address of variable for process identifier
);
Parameters
hWnd
Identifies the window.
lpdwProcessId
Points to a 32-bit value that receives the process identifier. If this parameter is not NULL,
GetWindowThreadProcessId copies the identifier of the process to the 32-bit value; otherwise,
it does not.
Return Values
The return value is the identifier of the thread that created the window.
OpenProcess
The OpenProcess function returns a handle of an existing process object.
HANDLE OpenProcess(
DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // handle inheritance flag
DWORD dwProcessId // process identifier
);
Parameters
dwDesiredAccess
Specifies the access to the process object. For operating systems that support security
checking, this access is checked against any security descriptor for the target process.
Any combination of the following access flags can be specified in addition to the
STANDARD_RIGHTS_REQUIRED access flags:
Access Description
PROCESS_ALL_ACCESS Specifies all possible access flags for the process object.
PROCESS_CREATE_PROCESS Used internally.
PROCESS_CREATE_THREAD Enables using the process handle in the CreateRemoteThread function
to create a thread in the process.
PROCESS_DUP_HANDLE Enables using the process handle as either the source or target process
in the DuplicateHandle function to duplicate a handle.
PROCESS_QUERY_INFORMATION Enables using the process handle in the GetExitCodeProcess and
GetPriorityClass functions to read information from the process
object.
PROCESS_SET_INFORMATION Enables using the process handle in the SetPriorityClass function
to set the priority class of the process.
PROCESS_TERMINATE Enables using the process handle in the TerminateProcess function
to terminate the process.
PROCESS_VM_OPERATION Enables using the process handle in the VirtualProtectEx and
WriteProcessMemory functions to modify the virtual memory of the
process.
PROCESS_VM_READ Enables using the process handle in the ReadProcessMemory function
to read from the virtual memory of the process.
PROCESS_VM_WRITE Enables using the process handle in the WriteProcessMemory function
to write to the virtual memory of the process.
SYNCHRONIZEWindows NT only: Enables using the process handle in any of the
wait functions to wait for the process to terminate.
bInheritHandle
Specifies whether the returned handle can be inherited by a new process created by the
current process. If TRUE, the handle is inheritable.
dwProcessId
Specifies the process identifier of the process to open.
Return Values
If the function succeeds, the return value is an open handle of the specified process.
If the function fails, the return value is NULL. To get extended error information,
call GetLastError.
Remarks
The handle returned by the OpenProcess function can be used in any function that requires a
handle to a process, such as the wait functions, provided the appropriate access rights were
requested. When you are finished with the handle, be sure to close it using the CloseHandle
function.
GetAsyncKeystate
The GetAsyncKeyState function determines whether a key is up or down at the time the function
is called, and whether the key was pressed after a previous call to GetAsyncKeyState.
SHORT GetAsyncKeyState(
int vKey // virtual-key code
);
Parameters
vKey
Specifies one of 256 possible virtual-key codes.
Windows NT: You can use left- and right-distinguishing constants to specify certain keys.
See the Remarks section for further information.
Windows 95: Windows 95 does not support the left- and right-distinguishing constants available
on Windows NT.
Return Values
If the function succeeds, the return value specifies whether the key was pressed since the
last call to GetAsyncKeyState, and whether the key is currently up or down.
If the most significant bit is set, the key is down, and if the least significant bit is set,
the key was pressed after the previous call to GetAsyncKeyState. The return value is zero if
a window in another thread or process currently has the keyboard focus.
Windows 95: Windows 95 does not support the left- and right-distinguishing constants.
If you call GetAsyncKeyState on the Windows 95 platform with these constants, the return value
is zero.
Remarks
You can use the virtual-key code constants VK_SHIFT, VK_CONTROL, and VK_MENU as values for the
vKey parameter. This gives the state of the SHIFT, CTRL, or ALT keys without distinguishing
between left and right.
SetTimer
The SetTimer function creates a timer with the specified time-out value.
UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
Parameters
hWnd
Identifies the window to be associated with the timer. This window must be owned by the
calling thread. If this parameter is NULL, no window is associated with the timer and the
nIDEvent parameter is ignored.
nIDEvent
Specifies a nonzero timer identifier. If the hWnd parameter is NULL, this parameter is ignored.
uElapse
Specifies the time-out value, in milliseconds.
lpTimerFunc
Points to the function to be notified when the time-out value elapses. For more information
about the function, see TimerProc.
If lpTimerFunc is NULL, the system posts a WM_TIMER message to the application queue.
The hwnd member of the message's MSG structure contains the value of the hWnd parameter.
Return Values
If the function succeeds, the return value is an integer identifying the new timer.
An application can pass this value, or the string identifier, if it exists, to the KillTimer
function to destroy the timer. If the function fails to create a timer, the return value
is zero.
Remarks
An application can process WM_TIMER messages by including a WM_TIMER case statement in the
window procedure or by specifying a TimerProc callback function when creating the timer.
When you specify a TimerProc callback function, the DispatchMessage function simply calls
the callback function instead of the window procedure. Therefore, you need to dispatch
messages in the calling thread, even when you use TimerProc instead of processing WM_TIMER.
The wParam parameter of the WM_TIMER message contains the value of the nIDEvent parameter.
TimerProc
The TimerProc function is an application-defined callback function that processes WM_TIMER
messages.
VOID CALLBACK TimerProc(
HWND hwnd, // handle of window for timer messages
UINT uMsg, // WM_TIMER message
UINT idEvent, // timer identifier
DWORD dwTime // current system time
);
Parameters
hwnd
Identifies the window associated with the timer.
uMsg
Specifies the WM_TIMER message.
idEvent
Specifies the timer's identifier.
dwTime
Specifies the number of milliseconds that have elapsed since Windows was started.
This is the value returned by the GetTickCount function.
Return Values
This function does not return a value.
Remarks
TimerProc is a placeholder for the application-defined function name.
Appendix - All the virtual keys with their values.
/*
* Virtual Keys, Standard Set
*/
VK_LBUTTON 0x01
VK_RBUTTON 0x02
VK_CANCEL 0x03
VK_MBUTTON 0x04 /* NOT contiguous with L & RBUTTON */
VK_BACK 0x08
VK_TAB 0x09
VK_CLEAR 0x0C
VK_RETURN 0x0D
VK_SHIFT 0x10
VK_CONTROL 0x11
VK_MENU 0x12
VK_PAUSE 0x13
VK_CAPITAL 0x14
VK_ESCAPE 0x1B
VK_SPACE 0x20
VK_PRIOR 0x21
VK_NEXT 0x22
VK_END 0x23
VK_HOME 0x24
VK_LEFT 0x25
VK_UP 0x26
VK_RIGHT 0x27
VK_DOWN 0x28
VK_SELECT 0x29
VK_PRINT 0x2A
VK_EXECUTE 0x2B
VK_SNAPSHOT 0x2C
VK_INSERT 0x2D
VK_DELETE 0x2E
VK_HELP 0x2F
/* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */
/* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
VK_LWIN 0x5B
VK_RWIN 0x5C
VK_APPS 0x5D
VK_NUMPAD0 0x60
VK_NUMPAD1 0x61
VK_NUMPAD2 0x62
VK_NUMPAD3 0x63
VK_NUMPAD4 0x64
VK_NUMPAD5 0x65
VK_NUMPAD6 0x66
VK_NUMPAD7 0x67
VK_NUMPAD8 0x68
VK_NUMPAD9 0x69
VK_MULTIPLY 0x6A
VK_ADD 0x6B
VK_SEPARATOR 0x6C
VK_SUBTRACT 0x6D
VK_DECIMAL 0x6E
VK_DIVIDE 0x6F
VK_F1 0x70
VK_F2 0x71
VK_F3 0x72
VK_F4 0x73
VK_F5 0x74
VK_F6 0x75
VK_F7 0x76
VK_F8 0x77
VK_F9 0x78
VK_F10 0x79
VK_F11 0x7A
VK_F12 0x7B
VK_F13 0x7C
VK_F14 0x7D
VK_F15 0x7E
VK_F16 0x7F
VK_F17 0x80
VK_F18 0x81
VK_F19 0x82
VK_F20 0x83
VK_F21 0x84
VK_F22 0x85
VK_F23 0x86
VK_F24 0x87
VK_NUMLOCK 0x90
VK_SCROLL 0x91
/*
* VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys.
* Used only as parameters to GetAsyncKeyState() and GetKeyState().
* No other API or message will distinguish left and right keys in this way.
*/
VK_LSHIFT 0xA0
VK_RSHIFT 0xA1
VK_LCONTROL 0xA2
VK_RCONTROL 0xA3
VK_LMENU 0xA4
VK_RMENU 0xA5
#if(WINVER >= 0x0400)
VK_PROCESSKEY 0xE5
#endif /* WINVER >= 0x0400 */
VK_ATTN 0xF6
VK_CRSEL 0xF7
VK_EXSEL 0xF8
VK_EREOF 0xF9
VK_PLAY 0xFA
VK_ZOOM 0xFB
VK_NONAME 0xFC
VK_PA1 0xFD
VK_OEM_CLEAR 0xFE
does not apply here!
You are deep inside fravia's page of reverse engineering,
choose your way out:
homepage
links
search_forms
+ORC
how to protect
academy database
reality cracking
how to search
javascript wars
tools
anonymity academy
cocktails
antismut CGI-scripts
mail_fravia+
Is reverse engineering legal?