The Ancient Art Of Training
Or What The Mega Trainer Gods Stuff Is All About
student
Not Assigned
04 June 1999
by A Nameless Stranger
Courtesy of Reverser's page of reverse engineering
slightly edited
by reverser+
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


Introduction

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.

Tools required

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.

Target's URL/FTP

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.

Program History

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.

Essay

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

Ob Duh,
does not apply here!

You are deep inside reverser's page of reverse engineering, choose your way out:


redhomepage redlinks redsearch_forms red+ORC redhow to protect redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_reverser
redIs reverse engineering legal?