Lokomotiv Trainer Engine
A trainer is a software program that modifies a game's memory and state to yield otherwise impossible effects - such as unlimited ammo, player invulnerability, and much more. Trainers are usually very small, and very repetitive programs - that is why it is frequent that engines for them are made. A trainer engine usually divides itself from the specifics, and instead provides the facilities to implement and carry out basic functions such as memory access, option timing and repetition, and user interface. Most involved trainer engines also implement advanced techniques such as code-shifting, DMA, triple injection, staple intersections and in-game menus (overlays).
Lokomotiv (aka 'loko') is one of the most feature-rich and easy to program trainer engines out there. Here are its features:
- very clever scripting system (implemented via FASM macros) that allows the trainer to carry out even the most complex operations with only a few lines of script code. For example, a simple switch that toggles player invulnerability (with patching inside a relocatable DLL module) can be scripted in only 5 lines of script code.
- Direct3D8/Direct3D9/OpenGL overlay ability, both runtime and non-runtime - meaning it can display overlays even if trainer was ran after the game!
- multiple trainers per executable - selectable from system menu (right-click on taskbar)
- built-in nfo/text-file viewer with custom font (independent of system settings) and character colouring
- anti-spy: encrypted runtime code and custom import address table
- anti-spy: direct read/write memory access (no ReadProcessMemory/WriteProcessMemory)
- anti-spy: breakpoint checks, debugger checks
Documentation
Documentation is still in the process of writing (and it will take a long time to do so). If anyone has time and the desire, they are welcome to create a reference for each of loko's instructions. In the mean time, it will be much more beneficial to look at the trainer scripts inside the /trainer directory. Most of the instructions are self-explanatory.
Sample Script
The following is a script for a real trainer for the game "Frogger's Adventures". It was generously donated by pizdabol, and contains advanced techniques such as double injection. You can refer to it while reading the description below.
frogger.inc;########################################################################## ; pizdabol 2004 ;########################################################################## target "Frogger's Adventures" wnd "Frogger's Adventures for Win" class "Frogger's Adventures for Win" exec 'FrogADV.exe' nfo 'trainers/frogger.nfo' onotes 'developer notes' message 'If you have Windows NT/2000/XP system, ',\ 'and ran this trainer before the game,',13,10,\ 'you can press F7 to see the trainer menu during the game.' overlaykey 'F7',VK_F7 ;########################################################################## option 'inject caves',OPTION_SET+OPTION_HIDDEN+OPTION_FREEZE oaddress 02140f1ch ocode mov dword [02140f2dh], eax movsx eax, word [eax+017eh] jump 004e2bcfh+7 ocodeend oaddress 004e2bcfh ocode jump 02140f1ch ocodeend oend ;########################################################################## option 'inc life',OPTION_ADDWORD onotes 'increase lives by 1' okey 'F5',VK_F5 oaddress '$',02140f2dh,'*','+',180h owords 1 oend ;########################################################################## option 'Left',OPTION_SCRIPT+OPTION_HIDDEN okey '',VK_NUMPAD4 oscript mread 02140F31h,OFFSET .nStep,4 mread 02140f2Dh,OFFSET .nAddress,4 add [.nAddress],00h mread [.nAddress],OFFSET .nPos,4 fld dword [.nPos] fadd dword [.nStep] fstp [.nPos] mwrite [.nAddress],OFFSET .nPos,4 retn .nAddress rd 01h .nStep rd 01h .nPos rd 01h oscriptend oend ;########################################################################## option 'Right',OPTION_SCRIPT+OPTION_HIDDEN okey '',VK_NUMPAD6 oscript mread 02140F31h,OFFSET .nStep,4 mread 02140f2Dh,OFFSET .nAddress,4 mread [.nAddress],OFFSET .nPos,4 fld dword [.nPos] fsub dword [.nStep] fstp [.nPos] mwrite [.nAddress],OFFSET .nPos,4 retn .nAddress rd 01h .nStep rd 01h .nPos rd 01h oscriptend oend ;########################################################################## option 'Up',OPTION_SCRIPT+OPTION_HIDDEN okey '',VK_NUMPAD8 oscript mread 02140F31h,OFFSET .nStep,4 mread 02140f2Dh,OFFSET .nAddress,4 add [.nAddress],08h mread [.nAddress],OFFSET .nPos,4 fld dword [.nPos] fadd dword [.nStep] fstp [.nPos] mwrite [.nAddress],OFFSET .nPos,4 retn .nAddress rd 01h .nStep rd 01h .nPos rd 01h oscriptend oend ;########################################################################## option 'Down',OPTION_SCRIPT+OPTION_HIDDEN okey '',VK_NUMPAD2 oscript mread 02140F31h,OFFSET .nStep,4 mread 02140f2Dh,OFFSET .nAddress,4 add [.nAddress],08h mread [.nAddress],OFFSET .nPos,4 fld dword [.nPos] fsub dword [.nStep] fstp [.nPos] mwrite [.nAddress],OFFSET .nPos,4 retn .nAddress rd 01h .nStep rd 01h .nPos rd 01h oscriptend oend ;########################################################################## option '1',OPTION_SET+OPTION_HIDDEN okey '','1' oaddress 02140f31h ofloats 10.0 oend ;########################################################################## option '2',OPTION_SET+OPTION_HIDDEN okey '','2' oaddress 02140f31h ofloats 20.0 oend ;########################################################################## option '3',OPTION_SET+OPTION_HIDDEN okey '','3' oaddress 02140f31h ofloats 30.0 oend ;########################################################################## option '4',OPTION_SET+OPTION_HIDDEN okey '','4' oaddress 02140f31h ofloats 40.0 oend ;########################################################################## option '5',OPTION_SET+OPTION_HIDDEN okey '','5' oaddress 02140f31h ofloats 50.0 oend ;########################################################################## option '6',OPTION_SET+OPTION_HIDDEN okey '','6' oaddress 02140f31h ofloats 60.0 oend ;########################################################################## option '7',OPTION_SET+OPTION_HIDDEN okey '','7' oaddress 02140f31h ofloats 70.0 oend ;########################################################################## option '8',OPTION_SET+OPTION_HIDDEN okey '','8' oaddress 02140f31h ofloats 80.0 oend ;########################################################################## option '9',OPTION_SET+OPTION_HIDDEN okey '','9' oaddress 02140f31h ofloats 90.0 oend ;########################################################################## option '',OPTION_SET oend option 'use 1-9 to select jump',OPTION_SET oend option 'distance, then numpad',OPTION_SET oend option 'arrows to move',OPTION_SET oend ;##########################################################################
A trainer script is composed of a single target block, and multiple option blocks. The target directive defines the title of the trainer ("Frogger's Adventures"), and starts of the block that describe the trainer target. Lokomotiv can identify the target process by its window title/classname pair ("Frogger's Adventures for Win"), or by its process name ("FrogADV.exe"). Only one of the two have to be present, but it is better to put it in both the window/classname pair, and the process name as backup. The nfo directive takes a path to the text file for the current trainer (relative to loko directory root), and incorporates it inside the executable for later viewing. The text file usually contains information for the user about the current trainer, who made it, and the description of each option. In the underground scene, this is usually know as the "NFO file". The message directive instructs the engine to display a message to the user. Use this to alert the user of important information (such as incompatibility between some two options). The overlay engine (DX8/DX9/OpenGL) is activated by the keyboard key that is specified by the overlaykey directive. The first parameter provides a textual, string representation of the key that is displayed to the user, and the next is the actual constant that tells loko which key to use (one of the VK_* constants in windows.h).
The first option block defines a single option called "inject caves". It is simple write (OPTION_SET), hidden from the trainer menu (OPTION_HIDDEN), and constantly rewritten (OPTION_FREEZE) option. There are multiple addresses inside the option block, which means both will be written to sequentially. The address of both options are fixed values. The first option writes the code inside the ocode block into the location $02140F1C. That means loko will assemble the code, and write its machine-code byte representation into the game's memory. This is very convenient, as you do not have to write code in a hexadecimal form anymore. However, you must be careful that the code's size does not exceed any set bounds. The second address in the same option block writes a simple jmp instruction. Lokomotiv contains a specialized macro called jump (note the extra 'u') that automatically calculates displacement between the current displacement and the target, and writes the proper encoding of jmp. The argument to jump is an absolute address in the target's address space.
The next option is used to increase the number of lives. It is marked as OPTION_ADDWORD, meaning it adds words (16-bit integers). The shortcut key for the option is VK_F5, displayed to the user as 'F5'. The address of the option is a DMA address. DMA stands for dynamic memory allocation, and is used to describe addresses of variables that are dynamic at run-time, but can be retrieved by reading a pointer. The syntax to deal with this kind of situation is very simple with loko - first start off with a fixed address of $002140F2, then read from it, and then add 180h to whatever was read. This is equivalent to doing the following in C:
int *ptr; ptr = 0x002140F2; ptr = *ptr; ptr += 0x180; // use ptr as the pointer to the number of lives
The amount to add to the memory is set by the owords directive. It is set to 1, meaning that each time the user presses F5, he/she will have one more life added. The number could also be negative. It is also possible to add bytes (8-bit integers), dwords (32-bit integers), qwords (64-bit integers), floats (32-bit decimals), and doubles (64-bit decimals). However, you must use the corresponding data directive, obytes for bytes, odwords for dwords, oqwords for qwords, ofloats for floats, and new odoubles for doubles.
The next four options (Left, Right, Up, and Down) are scripted options - meaning loko will not perform any operations, but will fire off the custom written script when the user presses the appropriate key. The custom written script should be put inside the oscript block (and ended by oscriptend). The script language is the same as x86 assembly, in FASM syntax (similar to NASM/TASM), with a few added on instructions - mread and mwrite. Both are responsible for memory access in the target's address space. The syntax for these instruction is as follows:
mread address, buffer, length mwrite address, buffer, length
address is the address of memory inside the target's address space. The memory is read from that address into buffer. The amount of memory read is set by the length parameter. The mwrite instruction is exactly the same, except the memory is written from buffer into address.
Scrips must end with retn instruction which yields control back to loko. Omitting that instruction will crash the trainer. If the script is to use any variables, they should be declared after retn. Variables are declared using this syntax:
.myvariable1 dd ? ; declare a DWORD variable named '.myvariable1' .myvariable2 dw ? ; declare a WORD variable named '.myvariable2' .myvariable3 db ? ; declare a BYTE variable named '.myvariable3' .myarray1 dd 5,6 ; declare an array of two DWORDs, set to 5 and 6 .myarray2 rw 20 ; declare an uninitialized array of twenty WORDs
Refer to FASM manual for more detailed description. In this particular example, the script declares 3 uninitialized DWORDs named .nAddress, .nStep, .nPos, respectively. Note that all variables and labels must begin with '.' inside the script block. The script reads the .nStep variable from the address $02140F31, and the .nAddress variable from $02140F2D. It then reads .nPos variable from the address pointed to by [.nAddress]. It loads .nPos onto the FPU stack, add the .nStep to it, and then saves it back to .nPos, and back into the target's memory at [.nAddress]. Finally, it returns to loko by using the retn instruction. The equivalent code in C would be:
void* nAddress; float nPos, nStep; ReadProcessMemory(hProcess, 0x002140F31, &nStep, 4, 0); ReadProcessMemory(hProcess, 0x002140F2D, &nAddress, 4, 0); ReadProcessMemory(hProcess, nAddress, &nPos, 4, 0); nPos += nStep; WriteProcessMemory(hProcess, nAddress, &nPos, 4, 0);
The other 3 options (Right, Up, and Down) execute exactly the same script code, but only differ in the arithmetics involving nPos. That is peculiar to the trainer itself, and begs no explanation from this document.
The next nine options are also hidden, and all they do is write a the 'jump distance' into a variable inside the target's memory address space that is later used by the Left, Right, Up and Down scripts.
The last few options have no functional purpose at all. Even though the options are of type OPTION_SET, which is used to perform a single write, there is no address specified, so loko writes nothing. This trick is used to display extra text in the trainer menu to give the user information about the trainer's functionalities. Such information could have been included in the text file specified by the nfo directive, but sometimes it is necessary to have information always displayed. These "dummy options" allow this.
Common Pitfalls
- Each option block must be closed with an oend directive.
- Each ocode block must be closed with an ocodeend directive.
- Each oscript block must be closed with an oscriptend directive.
- Blocks marked as ocode are written to target memory, and are not executed by loko. Use these blocks to overwrite code in the target's memory. However, oscript blocks are used to execute code by loko. An analogy would be that ocode is server-side code, and oscript is client-side.
- Each oscript must end with oscript, and its code must have a retn instruction. Each local variable and label inside the oscript block must be prefixed with a single dot ('.').
- To get the value of a local variable inside the oscript block, enclose it in square brackets, like so: mov eax, [.myvar]. For example, consider the following script:
oscript mov eax,[.ammo] ; eax is now equal to 100 mread $004002AA, OFFSET .ammo, 4 add [.ammo],10 mwrite $004002AA, OFFSET .ammo, 4 retn .ammo dd 100 oscriptend
As you can see, every operation that deals with the value of ammo, calls upon it with square brackets. Only in calls to mread and mwrite are the brackets not used, because mread and mwrite take the OFFSET to the buffer to be written.
Making Your Own Trainer
To make your own trainer using loko (you are encouraged to do so), it is required to perform a few simple steps:
- Make your own trainer script (probably copy an existing one and modify it). Save the script inside the /trainers directory. Include the reference to the trainer file inside the trainers.inc file (in the root directory).
- Make your own skin for loko. Skins are saved inside the /skins directory. Again, it is easiest to just copy an existing skin and modify it.
- Adjust the options for the trainer in the options.inc file. This file defines the skin that is used, and whether the trainer is compiled in debug or release mode. The debug mode allows you to analyze each step using the loko.log file. This file is not created in the release mode. Furthermore, the release mode toggles EXE encryption, debugger/breakpoint checks and code obsufication. This helps prevent bad people from using spy programs to steal your options.
- Run build.cmd
Known Issues
- If you make a mistake in the trainer script, or the skin includes, you may receive ambigious error messages from the FASM compiler. When you receive such messages, it is best to check which file the error was generated from. If it was in a trainer script, then check the Common Pitfalls section as you most probably have an error in your trainer script. If it is a skin include file, then check to make sure you have correctly set all the paths for resources (background bitmap, etc). If the error was from loko.asm or any other file, please file a bug report.
- Some security precautions have been disabled due to incompatibility with Windows XP SP2. These parts deal with anti-spying techniques. Further work and rewriting is needed.
- The overlay engine uses fixed addresses of Direct3D 9 procedures during runtime injection. They should be resolved dynamically.
- Permission flags should be checked for compatibility with No-Execute enabled chips.
- Overlay API hooks (particularly OpenGL) are not thread-safe.
License
The project is released under the BSD license.
About Loko
Lokomotiv was programmed in FASM, a very actively developed and free assembler for the x86 assembly language.
A big thanks goes to very good friend pizdabol who provided some very much needed beta-testing, and tried out the engine since its earliest version. Credits to cokine for the default skin, and ferrex for the overlay option notification font.
Download
loko-0.1.rar (313 KB)
Note: You need WinRAR to extract the file above.
References
Game Hacking BBS - a large and active forum where you can ask questions and exchange ideas about game training.
spookie - homepage of spookie. You can download trainers, tutorials, trainer tools and their source codes.
sheep - homepage of sheep. Appears to be down at the moment.
BRZI'S LAIR - homepage of Macedonian game trainer brzi. Contains many codes, trainers, tutorials and tools.
Comments
[an error occurred while processing this directive]