Making an in-game Lineage 2 Bot
Published on 2020-12-30 by xarkes
A few months ago I got nostalgic and wanted to try again a little bit of Lineage 2. In the past most servers were filled with tons of bots that would farm in order to either level up, gain money or loot materials useful for crafting equipment.
The working and available bots are all non-free (to my knowledge) and most of them are detected by the remote servers as cheating tools!
So I decided to make my own bot as I thought the process would be interesting. This blog post presents my approach at making my own bot for Lineage II.
Disclaimer
Although Lineage II is a dying MMORPG, botting is tolerated on some servers and forbidden on others as it may be considered as a cheating tool. You may get banned for life if you try such software on some servers.
Introduction
If you are not aware of what the concept of botting is, let's describe it before going further. You can get an idea of what a bot is by reading the Wikipedia page.
Lineage II is a MMORPG which demands a lot of farming. The main way to level up is to kill monsters, the main way to get money is to kill monsters and the main way to craft better equipment is to kill monsters. This may sound like a boring game but Lineage II offers in my humble opinion a very unique player versus player (PvP) gameplay style that makes the game much more interesting than many other MMORPGs.
Because it requires a lot of farming, being able to automate the action of killing monsters helps a lot the player. One can let its character farm during the night and play during the day and get a lot of rewards.
The bot I designed will be able to target a nearby monster, attack it and use spells, pickup items and rest when needed.
Lineage II is protected with a (probably old) version of WinLicense, so some functions in the executable file may be virtualized but it won't be of any matter to us. Also, game servers use protections like GameGuard, SmartGuard or LameGuard in order to prevent botting, and for some reason my bot is not detected at all by any server I connected to.
Lineage II has a lot of public private server emulators and various researches around packet hacking so the overall task was not very hard to achieve as many resources are available online.
Making the bot
Design choice
I can see two different kind of bots. Full Out-of-Game (OOG) bots, and In-Game bots (IG).
The difficulty of making an OOG bot is that it requires to reverse engineer, understand and implement the whole client-server protocol (including network encryption). However it may be quite rewarding as then it's possible to run the bot without a copy of the game. That means it will be much lighter and much easier to run multiple instances of the bot.
In the case of Lineage II, making an IG bot is much easier as Lineage II is implemented in such way that the client will not predict any action, but only react to what the server sends. That means we can simulate the sending of a packet using the running client, and that's just it, the game client will react to that packet we sent after the server validates (or not) the action. However running an IG bot might be tedious as it will be a little bit intrusive and might trigger anti cheat detections.
I decided to go with the IG bot as Lineage II is an old game I suspected if it had any anti-cheat it would not be too hard to bypass.
Sending a specific packet
Finding the send packet function
As my purpose was to automate things, my first objective was to send a chosen packet so I could automate single tasks.
Lineage II works in a full TCP fashion and connects first to a login server which centralizes multiple game servers. The next steps describe the sending of a packet when already in game and connected to a game server.
In order to send a packet, I needed to find the function that would send a packet. The game uses the function send
from the native Windows library ws2_32.dll
. All I had to do was to set up a breakpoint there, and do an action in game to trigger it. The parameters given to the send
function contain the encrypted packet, so we need to check from where the send
function is called thanks to the stack frame. From there it is possible to do a bit of reverse engineering and understand what's happening.
The screenshot above shows a call to the send packet function when I type in a message to send in the game chat. The first value on the stack frame is the return address, and the followings are the function arguments. The first argument is a fixed pointer to some data structure which contains the Windows TCP socket. The second argument is a null terminated string for which each character specifies the type of the packet field. In our case (sending a chat message) the packet contains the following arguments:
- "c" (
char
) :0x49
which corresponds to the packet id "send message", - "S" (
String
) :L"Hello"
which is a pointer to a null terminated wide string, - "d" (
dword
) :0x00000000
which corresponds to the channel ID.
Replaying a packet
In order to call the "send packet" function I decided to inject a DLL into the game process that would create a thread and call this very function.
The DLL code is rather small:
DWORD SendPacketFP;
DWORD(_stdcall* sendPacket)(DWORD FP, const char* format, ...);
#define DATA_SEND_ADDR 0x3E3B80 // Offset of the "send packet" function in engine.dll
#define DATA_SEND_SOCKET_INFO 0xFD890000 // Offset of the networking structure
void ProcessAttach()
{
DWORD EngineDLLBase = (DWORD) GetModuleHandle(L"engine.dll");
sendPacket = (DWORD(_stdcall *)(DWORD, const char*, ...)) EngineDLLBase + DATA_SEND_ADDR;
SendPacketFP = DATA_SEND_SOCKET_INFO; // Pointer to the network structure
const char format[] = "cSd";
const WCHAR* message = L"Hello";
DWORD parameters[3] = { 0 };
parameters[0] = 0x49; // Packet ID
parameters[1] = (DWORD) message; // Pointer to our message
parameters[2] = 0x00000000; // General chat ID
sendPacket(SendPacketFP, format, parameters[0], parameters[1], parameters[2]);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ProcessAttach, 0, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
There are multiple ways to inject the DLL inside the game process. I decided to make my own injector which is very simple and will:
OpenProcess
the game processl2.exe
- Allocate memory in the game process address space with
VirtualAllocEx
- Write the
.dll
full path withWriteProcessMemory
into that space - Load the library with
CreateRemoteThread
which will callLoadLibraryW
Now when I load the library, a packet is sent to the server and we can observe the result directly in game.
That's cool, now I can make actions in the game, but I also need to get information from the surrounding environment.
Getting insights from the game
In order for the bot to work, it is required to gather information from the game such as player current position, health, inventory, skills, but also nearby entities such as Non Playable Characters (NPCs) or nearby players or monsters.
To do so, I can see only two ways: either inspect the game memory, which involves finding the position of all these structures in the game virtual address space, or just parse the received packets from the server as the game client does. I decided to go with the second way, as I thought it would be hard to gather reliably every information I needed with only memory inspection. However on the long run, it might have been easier as tnere would have been no need to understand the client-server protocol.
Similarly to what I've done earlier, it's possible to put a breakpoint on the recv
function from ws2_32.dll
and check the stack frame and try to find which function is calling recv
. After that we can try to step over every function until the packet gets decrypted and we can start parsing it.
In order to notify my bot that a packet is received, it is required to hook the "receive packet" function so I can dump the packets and then give the execution flow back to the game.
LPVOID Hook(LPVOID functionToHook, LPVOID myFunction, size_t size)
{
DWORD old;
DWORD old2;
// Allocate memory and copy the old bytes (original instructions) there
oldInstructions = malloc(5 + size);
VirtualProtect(oldInstructions, size + 5, PAGE_EXECUTE_READWRITE, &old);
memcpy(oldInstructions, functionToHook, size);
// Add a jump after the copied bytes from the hooked function
// to jump back to the rest of that hooked function.
// This allows that when someone calls oldInstructions, it acts as the original functionToHook
*(BYTE*)((DWORD)oldInstructions + size) = 0xE9;
*(DWORD*)((DWORD)oldInstructions + size + 1) = (DWORD)((DWORD)functionToHook + size) - (DWORD)((DWORD)oldInstructions + size) - 5;
// Patch the function to hook in order to jump to our own function
VirtualProtect(functionToHook, 5, PAGE_EXECUTE_READWRITE, &old);
*(BYTE*)functionToHook = 0xE9;
*(DWORD*)((DWORD)functionToHook + 1) = (DWORD)myFunction - (DWORD)functionToHook - 5;
VirtualProtect(functionToHook, 5, old, &old2);
return oldInstructions;
}
In the function above you might wonder what 5
refers to, it's actually the length of the jmp
instruction.
In order to understand the packets and their meaning, there are tons of resources online describing the packets for almost every version of Lineage II, so the reverse engineering task here is almost non-existent.
Bot Architecture
Software design
Now that I am able to receive a packet and send a packet to the game, I decided to come up with the following architecture:
As seen in the picture, I first inject the DLL which will automatically create a named pipe. The pipe will be used so that when the game receives a packet, the bot gets notified a packet was received, and similarly when the bot needs to interact and do an action in game, it can send it through this socket.
Thanks to this architecture I can seemlessly inject and remove the bot from the game process as well as handling multiple game instances.
For the graphical interface, I decided to go with Qt for no specific reason appart that I was already familiar with it.
Bot automata
In order for the bot to be "smart" I decided to go with a sort of automata which may react to external events. The current automata can be described as follows:
In reality it is much more complex as many things may happen at any time! For instance, you may reach the Start
state with a very low health, and you don't want your character to start hitting monsters with low Health Points (HP) so it is preferable to jump to the Rest
state. Also you have to keep in mind that nothing is instantaneous, so many checks must be done very often. When choosing a monster to attack and attacking it, usually when you play for real you do not want to attack a monster that is already attacked by a player. To mimick this behavior, it is important to check at multiple states if the current target is still valid. Also it is important to think about every corner case. The bot might select a monster which has already been one shotted by another player by the time the targeting was done, and you don't want the bot to be blocked in such case, hence my choice of having Choosing
, Target
, Targeted
, Engage
and Engaged
.
So every state will have its own set of checks and do the action only if every check successfuly passed, otherwise it will jump back to Start
.
When an event is received, for example system messages suchs as "Cannot see target" or "Invalid target", I decided to simply check in what state the automata is and react accordingly.
Results
In the end, the bot works nicely and is not detected by any server I tried even after hours of farming. I managed to run 4 instances simultaneously on the same computer and on the same server for hours without any hassle, while I am aware that other botting tools get detected immediately. This lets me think that every "anti-bot" system that are purchasable online are actually only checking for known bots or signatures and do not even attempt to do some "advanced" analysis.
I am aware some server are more sophisticated and ask for a captcha after a certain amount of time, but I think those are custom "home made" protections which might be a bit annoying for the players, but makes it harder for someone who uses a bot to defeat (although I found some working in-game captcha breaker implementations online).
That's pretty much it, in the end the bot was quite simple to build and I'm happy with the results even though there is still much to do. However I am afraid my nostalgia has gone and that I won't work much on this little project.
You can get the source code on my GitHub repository: https://github.com/xarkes/L2Bot.
There are still many things to improve for the bot and if you are interested, you can check it in the next section.
Going further
The current bot permits to have something working but nobody likes stupid bots, so below are some features I think would be good to have in order for the bot to be usable in every situation:
- Add automatic Lineage 2 version detection and proper multiple protocol version support
- Support automatic spoil and sweep feature
- Support self buff
- Support party buff
- Support items usage (potions, scrolls, ...)
- Add conditions for item/buff/skill usage
- Automatically parse game data in order to get and display icons for items, skills, NPC names, etc.
- Fun fact:
.dat
files of Lineage 2 are encrypted with RSA which means the client can decrypt it with the public key, and only the game publisher can create a valid.dat
file (given that the RSA key is not too weak). - Basically it is a signature, however it's
dirtyfunny that they just check if the decryption result is a valid file format
- Fun fact:
- Add on death actions (go to town, disconnect, ...)
- UI improvements (better map resizing, ...)
- Better party interactions (allow a character to assist another one, to buff, heal, etc. when needed)
- Add a scripting engine
- Might be awesome to just record actions to automate some quests, or other various things.
- Add a "no go" zone feature to avoid obstacles that are obstructing the character line of sight when trying to attack a monster
That's it, feel free to reach me if you have any question, I hope you enjoyed!