Post Last Modified: 9:09 PM 5/31/2011(New Zealand Time GMT+12)
Heya everyone, I am working on the TSXClient as part of the private server project. I will be developing TSXClient over quite a while and posting up information on parts I add with the hope that knowledge can be taken from this project and applied to other things to help others learn.
Breif
To create a DLL that enhances the current game client to improve on things that could of been done better, whilst enabling it to connect to the private server.
Disclaimer
This software is used by the user at their own risk. Game data is not distrubited, the source code is not part of TS2.
All I am doing is documenting my work and findings, if you find my work useful then thank me, add special thanks too MegaByte in your software you make.
This is for educational purposes only.
I already have
made so GFX and what not can be improved.I will be editing this post every time I add new content.
Please feel free to ask questions but only if you have read everything in this topic. Please keep in mind that the TSXClient is about improving client features.
Modifications that I know I will be trying:
- Zoom Hack - Because the current zoom levels are fail
- Fixing background at Character Select to make it relevant to faction of selected character and or level.
- Improving game CPU Usage *the main game loop does not have a Sleep(1) causing it to consume CPU usage when it does not need to.
- Swear Filter Enable/Disable I can even have my own words in swear filter list 001.DAT in GFont
- Increased walking speed?
- Improve game sounds
- Improve game GUI
- Improve BGM
- Economy change by editing items
- Show enemy player names above their heads along with their faction and contribution
- Option to always show monster names above their heads
- Reverse AutoPill
- Pet Feeder
- Improved looting? Might just do this server side
- More to come when I think of them....
Software Im Using
Visual Studio 2008 -

Cheat Engine -

OllyDBG -

HexWorkshop - Its not free buy it if you like it
WPE PRO -

Part 1 - Making the project
Create a New Empty Win32 DLL Project:
- Create a New Project ( Ctrl+Shift+N )
- Select Win32 Project
- For the Name enter TSXClient
- Set the location you want the project saved in For example C:\Source\TSXClient
- Click OK
- Click Next
- Set the Application type to DLL
- Tick Empty project under Additional options
- Click Finish
The project has been made now to add the first Header and CPP files
Header files contain defines of data types, methods/functions and structures/class's *they can also contain code but its best to keep it at a minimum and have the code in the CPP source file.
Create TSXClient.h and TSXClient.cpp
- Click Project | Add New Item or press Ctrl+Shift+A
- Select Header File (.h)
- Type in TSXClient.h
- Click Add
- Create also TSXClient.cpp by repeating above step too add an item
- And then selecting C++ File (.cpp) and typing in TSXClient.cpp for the Name
Starting TSXClient.h
TSXClient.h Will contain our defines
Put the following code inside it
Code:
#ifndef __TSXCLIENT_H
#define __TSXCLIENT_H
#pragma message("DLL Source code by MegaByte")
// Include Header Files
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Define varables
HANDLE TheMainThread;
// Define methods/functions
BOOL APIENTRY DllMain (HINSTANCE hInstance, DWORD reason, LPVOID reserved);
DWORD WINAPI MainThread(LPVOID);
DWORD Initilize();
DWORD Uninitialize();
// For information on Dynamic Link libarys see
// http://en.wikibooks.org/wiki/Windows_Programming/Dynamic_Link_Libraries
#endif
The first two lines with the ifndef and define are known as Header guards
They prevent the h file having its contents linked more than once which would cause duplicates. There is an #endif at the bottom all of the header files contents for defining things is inside this block.
#pragma message is just a message that will be displayed in the Output window when compling this header file.
#define is used to define something in this case we are setting a define of WIN32_LEAN_AND_MEAN which means when we include windows.h it will not include things rarely used. Whilst im not sure why this is I think it means our file size is smaller or something. Why load things we dont need?
Define Varables is where we define varables we are going to be using Globally in our TSXClient.cpp.
Such as TheMainThread it is a Handle to the MainThread when we create it. We can use it to Terminate the thread later when the program wants to close *so that it does not crash*
Defining our methods/functions is where we do just that.
DLLMain is the main function initilized when the DLL is injected/loaded into a process and when the process is exiting/unloading the DLL.

Initilize is a method that will find all of our signitures, setup data in memory load files load class's, setup hooks etc.
Uninitialize is a method that will remove all of our hooks and do other operations so that the program can shut down successfully.
If an error is returned then our DLLMain can return false and the program will show an error.
Starting TSXClient.cpp
TSXClient.cpp will contain our code for the methods defined in the TSXClient.h
Put the following code into TSXClient.cpp
Code:
#include "TSXClient.h"
BOOL APIENTRY DllMain (HINSTANCE hInstance, DWORD reason, LPVOID reserved)
{
DisableThreadLibraryCalls((HMODULE)hInstance);
if ( reason == DLL_PROCESS_ATTACH )
{
TheMainThread=CreateThread(NULL, NULL, MainThread, NULL, NULL, NULL);
}
else if ( reason == DLL_PROCESS_DETACH )
{
// Exit
if (Uninitialize()!=0)
{
return FALSE;
}
}
return TRUE;
}
DWORD Initilize()
{
MessageBox(0,"Hello World","Test from my DLL",0);
// Find Signitures
// Apply Patchs
// Setup Hooks
return 0;
}
DWORD Uninitialize()
{
// Terminate MainThread
TerminateThread(TheMainThread,0);
// Put code back to normal if required
// Remove Hooks
return 0;
}
DWORD WINAPI MainThread(LPVOID)
{
DWORD Result = Initilize();
if (Result!=0)
{
// Error happened find out what the error was and take action
}
// Do this loop forever (whilst this thread is running)
while(1)
{
// Operations can be done here such as things to do with timers or checks of game status.
Sleep(1); // Sleep so that CPU is not fully consumed
}
}
Save Everything and build the solution (F7)
A TSXClient.dll will be created this can be injected into the game.
I have already made an Injector in my launcher, there are plenty of injectors both in source code and binary on the internet. just google for source code or DLL Injector or something.
What have we got so far?
Well this is our basic DLL when we inject it into any application we will see a Hello World message box popup. When we close the application it will not error so we know that our code works so far.
Part 2 - Adding some functionality
Well we want the DLL to do something so lets make it able to do the following things:
Changing the Window Title
We want to be able to see TSX Project <Version Number> or something similar in the window title.
We can do this in the Initilize() method we will have to add some varables and code.
In TSXClient.h under Handle TheMainThread;
put the following
Code:
DWORD ProcessID; HWND WindowHandle; LPCTSTR WindowTitle="TSX Project";
In TSXClient.cpp in Initilize()
Remove the MessageBox line and put in the following code at the bottom under // Setup Hooks
Code:
// Change window title
ProcessID = GetProcessId(GetCurrentProcess());
DWORD test;
WindowHandle=0;
bool Done=false;
// Find the window handle of our processes main window
while (Done==false)
{
WindowHandle = ::GetTopWindow(0); // Get the topmost window
while ( WindowHandle ) // While there is a window to check
{
GetWindowThreadProcessId(WindowHandle,&test); //Get process ID window belongs to
if ( test == ProcessID) // Check if it is our process
{
if (GetParent(WindowHandle)==0) // Check if window has no parent window
{
Done=true; // Window handle has been found
break;
}
}
WindowHandle = GetNextWindow( WindowHandle , GW_HWNDNEXT); // Get next window
}
}
// Set the window title
SetWindowText(WindowHandle,WindowTitle);
We have to keep looking for it because at this stage we could inject our dll before the window is created. That is why we have a while loop to keep the code searching if it is not Done.
The window handle has been found when the ProcessID of the found window handle is equal the process id of our process.
Adding Log File
We want to be able to see what is going on as a text output.
Something simple like Log("Something just happened"). It should allow us functionality like printf has.
I suppose we could also Allocate a console window if we wanted but we would require another thread if we wanted user input on it. You would have to AllocConsole in Initilize then FreeConsole in Uninitilize.
For logging I will be using a singleton class of my Log Class I have already made.
It Consists of 3 files CSingleton.h, CLog.h and CLog.cpp
A singleton class is a class that there can only ever be one of.
CSingleton contains a template class to make any class a Singleton, it can be handy for things like a class to store program wide settings or Global varables/methods. Or for log files or even in the pserver code the LoginServer, WorldServer,MapManager class's.
Create CSingleton.h
Put in this code
Code:
#ifndef __CSingelton_H
#define __CSingelton_H
// A Singleton template class
// Usage make a file for example CMyClass
// include CSingleton.h
// class CMyClass : public CSingleton<CMyClass>
//{
// public:
// CMyClass(){};
//};
// static CMyClass * MyClass = CMyClass::Instance();
// In any cpp/h file that includes CMyClass.h you can go MyClass. And access its methods
template <typename T>
class CSingleton
{
public:
static T* Instance()
{
// Create Instance if it does not exist
if (m_instance==0) {m_instance = new T;}
return m_instance;
};
static void DestroyInstance()
{
// Destroy Instance if it exists
if (m_instance!=0)
{
delete m_instance;
m_instance = 0;
}
};
protected:
// Protect constructor and deconstructor
CSingleton(){};
virtual ~CSingleton(){};
private:
// Copy constructor
CSingleton(const CSingleton& source){};
static T* m_instance;
};
template <typename T> T* CSingleton<T>::m_instance = 0;
#endif
Code:
#ifndef __LOG_H
#define __LOG_H
#include <iostream>
#include <fstream>
#include <string>
#include <list>
#include <stdio.h>
#include <stdarg.h>
#include "CSingleton.h"
using namespace std;
class CLog : public CSingleton<CLog>
{
public:
char * LogFile;
//std::list <std::string> history;
CLog(char* FileName = "LogFile.log");
void Write(const char *fmt, ...);
void Remove();
void SetFile(char* FileName);
};
static CLog * Log = CLog::Instance(); // Create the Log Singleton
#endif
Code:
#include "CLog.h"
CLog::CLog(char* FileName)
{
LogFile=FileName;
}
void CLog::Write(const char *fmt, ...)
{
char buf[1024] = {'\0'};
va_list va_alist;
va_start(va_alist, fmt);
vsprintf_s(buf, fmt, va_alist);
va_end(va_alist);
//history.push_back(buf);
ofstream myfile;
myfile.open (LogFile,ios::app); // Open LogFile append to end
myfile << buf << endl; // Write out the message
myfile.close(); // Close LogFile
}
void CLog::Remove()
{
remove(LogFile);
}
void CLog::SetFile(char* FileName)
{
LogFile=FileName;
}
Code:
#include "CLog.h"
So go ahead add the following to the very top of TSXClient.cpp Initilize()
Code:
Log->Remove();
Log->Write("TSX Client Started");
You can do this by opening the propertys for the TSXClient project in the solution explorer then click and expand Build Events click Post-Build Event and in the Command Line field put
Code:
copy $(OutDir)\$(ProjectName).dll GAMEPATH /Y
Signiture Finding
Our project so far is not much use if it cant find signatures so lets add functionality so it can find them.
What is a signiture?
A search string of bytes with wild cards.
We can use a signature to find Assembly code in memory and thus addresses to variables.
Why do we use signatures?
Why not just use the address when we find it with Cheat Engine?
Well addresses change if the game is recompiled/patched.
Chances are the game code wont change too much, why would a team of programmers recode something if its working fine? So we can use already existing code to find the new addresses so that we can do things we want to do with them.
Shortly put Signature Scanning allows our code to work fine even after the exe is updated providing the code we made the signature from still exists and has not been modified.
Signiture Scanning, the code
In TSXClient.h under DWORD Uninitilize(); put
Code:
bool Compare(const BYTE* pData, const BYTE* bMask, const char* szMask); DWORD FindPattern(DWORD Address,DWORD offset,BYTE* bMask,char* szMask);
Code:
// Signiture Scanning Methods
// I suggess special thanks to dom1n1k for this
// http://www.gamedeception.net/threads/5484-Self-Updating-EngPtrs
// It works by going through the memory from start address through to offset
// Searching for the Bytes in the bMask providing it matchs up with szMask.
bool Compare(const BYTE* pData, const BYTE* bMask, const char* szMask)
{
for(;*szMask;++szMask,++pData,++bMask)
if(*szMask=='x' && *pData!=*bMask )
return false;
return (*szMask) == NULL;
}
DWORD FindPattern(DWORD Address,DWORD offset,BYTE* bMask,char* szMask)
{
for(DWORD i=0; i < offset; i++)
if( Compare( (BYTE*)( Address+i ),bMask,szMask) ) return (DWORD)(Address+i);
return 0;
}
We wildcard all Addreses. Because these can change. Instructions won't change, and neither will some numbers.
We can search for a signiture like this
Code:
PBYTE SignitureBytes="\xA1\x00\x00\x00"; char * SignitureMask = "x????"; DWORD Address = FindPattern(0x00500000,0x006FFFFF,SignitureBytes,SignitureMask);
If the memory region is invalid the program will crash.
Something else we will want to do is add some code to Initilize so that Signitures are only scanned for when the exe is unpacked. With Twelve Sky 2 I know that the exe is unpacked the moment we can see the libary D3D9.dll so we can enter this code into TSXClient.cpp just before // Find Signitures on the line above
Code:
// Wait till D3D9.dll is loaded
DWORD DLLHNDL=0;
while(!DLLHNDL)
{
DLLHNDL = (DWORD)GetModuleHandle("D3D9.dll");
}
Sleep(2000); // Wait longer just in case
So now we need to test this out on something I choose...
Zoom Hack
Now that we have signature scanning working we can add in the Zoom Hack.
Finding the hack
There is a tutorial on how to find this in the Hack release section but I will explain to you how to do it.
Open Game
Login
Select a character and goto world
Stand somewhere where we can zoom in and out freely
Zoom all the way out
Open Cheat Engine 6.0 32 bit as Administrator
Setting up Cheat Engine 6.0 32bit
I am assuming you have not setup Cheat Engine 6.0 the way I like by the way I Highly recommend you do the tutorial that comes with Cheat Engine.
Click Settings
Under General Settings:
- Set Update interval, Freeze interval to 1 and Found address interval to 10
Under Debugger Options:
- Use int3 instructions for breakpoints
- Use VEH Debugger
Under Extra:
- Enable use of the Process Watcher
Click OK
Attach Cheat Engine to Twelve Sky 2 click the button in top left:
Select TwelveSky2 in the list and click Open
Set the Value Type to Float
Set Scan type to Unknown Initial Value
Click First Scan
Zooming In decreases the Current Zoom Value
Zooming Out increases the Current Zoom Value
Methods to find Current Zoom Value
Method A
Zoom in slightly
Scan for decreased value
Method B
Zoom out slightly
Scan for increased value
Method C
Do nothing in game
Search for unchanged value
Repeat A B and C as needed until you find a static (Green) address for the Current Zoom Value.
You will know when you have found it because when you add it too the list and freeze it you will be unable to zoom in our out. To clear things up when you are zoomed all the way in the value is 25 and when your zoomed all the way out it is 150
We know that +4 from this address is Min Zoom and +8 is Max Zoom
So we now need to find ASM code that uses this Current Zoom Address.
There are two ways we can find this.
We can right click the address and find what accesses it then zoom in or out and look at the code cited.
Or we can search for the address of Current Zoom Value as Hex 4Byte then goto that address in code view and scroll up a bit.
Either will work.
Once we find the ASM that uses this address we can create a signature from it.
Code:
00474212 - D9 05 00D41501 - fld dword ptr [0115D400] : [(float)123.9997] 00474218 - D8 4D E4 - fmul dword ptr [ebp-1C] 0047421B - D8 45 F0 - fadd dword ptr [ebp-10] 0047421E - D9 5D B8 - fstp dword ptr [ebp-48] 00474221 - D9 05 00D41501 - fld dword ptr [0115D400] : [(float)123.9997]
D9 05 ?? ?? ?? ?? D8 4D E4 D8 45 F0 D9 5D B8
Then once we have the address of that +2 then reading 4 bytes will get us the address.
I have made the Address's ?? because they can change when game is recompiled.
I can check this by searching for Byte Array using Cheat Engine and putting in the above signature. (Make sure Hex is checked)
I have two results the C++ code will only use the first one. The first one is the address of the code above [00474212]
Now we can make this signature code and alter the Minimum Zoom and Maximum Zoom values using our DLL.
You can see how it works if you look closely in the mask the x is if the byte has to be exact and a ? is if it can be anything. In the Pattern we just put the bytes in because the wild card bytes do not matter we just make them 00.
In our TSXClient.h under "LPCTSTR WindowTitle="TSX Project";" lets put the following
//D9 05 ?? ?? ?? ?? D8 4D E4 D8 45 F0 D9 5D B8
Code:
// Memory Things // Zoom Hack float MinZoom = 0.00f; float MaxZoom = 500.00f; DWORD CurrentZoomAddress = 0;
Code:
CurrentZoomAddress = FindPattern(0x00400000,0x004FFFFF,(BYTE*)"\xD9\x05\x00\x00\x00\x00\xD8\x4D\xE4\xD8\x45\xF0\xD9\x5D\xB8","xx????xxxxxxxxx");
Log->Write("Zoom Signiture found at: %X",CurrentZoomAddress);
CurrentZoomAddress = *(DWORD*)(CurrentZoomAddress+2);
Log->Write("Current Zoom Address: %X",CurrentZoomAddress);
Code:
// ZoomHack *(float*)(CurrentZoomAddress+4) = MinZoom; *(float*)(CurrentZoomAddress+8) = MaxZoom;
The code above finds the signature we have made by using FindPattern then 2 is added too that and the address for CurrentZoom is read. An explanation of how to read/write memory is below.
We can then set the min and max values what we are doing is adding the appropriate amount to the current zoom address and casting it to a float* then we are referencing that float* as a float which we are setting to the appropriate zoom level.
Writing and Reading Memory
Well previously some of you may have used the apis for doing this they are WriteProcessMemory and ReadProcessMemory if you used these you would of also used OpenProcess I think it is called.
Because our DLL is injected in the game we do not need to use these shockingly slow APIs.
So how do we access an address? We can use pointers in C++
We need to store the address in a DWORD we can cast this to a pointer of any data type we want. In short Pointers point to memory addresses. A pointer is a data type of 4 bytes *on 32bit* that stores the address of another memory location.
CurrentZoomAddress contains our address
(float*) means we are casting the value after it to a pointer to a float data type.
(CurrentZoomAddress+4) takes adds 4 too CurrentZoomAddress this is the address of Maximum Zoom.
We then use a * after it to let the compiler know we want to reference the Address. This enables us to write/read the value stored in that address. Because we have casted to a float* the data type when referenced is a float.
You can read about operators here
Detouring Libary
The detouring libary is something we can use to easily detour game methods/functions or Windows API methods/functions.
There are many different detouring libarys some ones I have used are, Microsoft Detours, DetoursX, Detours 2.5 and there is a "new" one I want to try called MologieDetours which is avaliable on Game Deception. Its cross platform which may be usefull for future projects when hacking games on Linux or Mac.
There are also many ways to detour something.
Detours work by making it so that when the address of the function you want to detour is read it jumps to your own code.
We copy the code we overwrite so we can undetour or call the origional function.
Theres a bit of basic maths involved and possibly nessecary a function to get the size of an ASM command + paraamters in bytes. Some of the less developed detour libarys just make you enter this your self.
Which is not a problem as you know that you need 5 bytes for the JMP AddressToGoto so the first 5 bytes need to be modified and nops put in after if padding is requierd.
Here is a picture trying to explain how detours work.
So anyway I have setup MologieDetours in my project by downloading it extracting its files and putting them where my TSXClient.cpp is stored on disk.
Then I have dragged the files I have added into the solution TSXClient in the solution explorer window.
I have added an include for detours.h to TSXClient.h under the CLog.h include line.
Setting up a FileOpen hook
Now that we have Detouring added we can setup a hook on
.This will allow us to make the game load custom content.
by looking at the Syntax of these functions on MSDN we can make typedefs for them. We put this in TSXClient.h under window title
Code:
// Detour Stuff typedef HANDLE ( WINAPI* tCreateFileW )(LPCWSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile); HANDLE WINAPI hook_CreateFileW(LPCWSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile); MologieDetours::Detour<tCreateFileW>* detour_CreateFileW = NULL;
Code:
try
{
HMODULE Kernel32 = GetModuleHandle("Kernel32.dll");
// Currently there appears to be an error with MologieDetours when creating a new Detour of a Module Function
// I have told the developer about it and a solution I thought of. But until he does something about it I will just comment the code and use what it should be doing
//detour_CreateFileW = new MologieDetours::Detour<tCreateFileW>(Kernel32,"CreateFileW", hook_CreateFileW);
detour_CreateFileW = new MologieDetours::Detour<tCreateFileW>((tCreateFileW)GetProcAddress(Kernel32,"CreateFileW"),hook_CreateFileW);
}
catch(MologieDetours::DetourException &e)
{
// Handle error
return 10;
}
Code:
delete detour_CreateFileW;
At the bottom of TSXClient.cpp put
Code:
// HOOK Functions
HANDLE WINAPI hook_CreateFileW(LPCWSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile)
{
HANDLE Result;
// Get custom file path
wchar_t CustomFile[MAX_PATH]=L"TSX\\";
wcscat(CustomFile,lpFileName);
// See if custom file exists (By setting CreationDisposition to OPEN_EXISTING the API will only open the file if it exists)
if (detour_CreateFileW->GetOriginalFunction()(CustomFile,dwDesiredAccess,dwShareMode,lpSecurityAttributes,OPEN_EXISTING,dwFlagsAndAttributes,hTemplateFile)!=INVALID_HANDLE_VALUE)
{
// Our File Exists
Result = detour_CreateFileW->GetOriginalFunction()(CustomFile,dwDesiredAccess,dwShareMode,lpSecurityAttributes,dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile);
}
else
{
// Use origional File
Result=detour_CreateFileW->GetOriginalFunction()(lpFileName,dwDesiredAccess,dwShareMode,lpSecurityAttributes,dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile);
}
return Result;
}
Just incase your wondering concatenate means to join together.
Also I noticed a module I can check for thats loaded pretty much straight after the game is unpacked because it is a system hook or something "uxtheme.dll" is on XP,Vista,Seven it is for things to do with the graphical user interface.
So replace this
Code:
// Wait till D3D9.dll is loaded
DWORD DLLHNDL=0;
while(!DLLHNDL)
{
DLLHNDL = (DWORD)GetModuleHandle("D3D9.dll");
}
Sleep(2000); // Wait longer just in case
Code:
// When uxtheme is loaded the game is unpacked
HMODULE uxtheme = GetModuleHandle("uxtheme.dll");
while (!uxtheme)
{
uxtheme = GetModuleHandle("uxtheme.dll");
}
There are two ways to do this, we could detour winsocks connect api or we can change the IP address in the games memory. We will do the second.
First of all we need to know the IP Address of the Login Server.
We can find this out using WPE Pro
Run the game click the first server button
Run WPE Pro attach to Twelve Sky 2
Start Listening / Recording Packets
Go in game Click the first server button then click Cancel
You will be taken back to server select and the game will connect to each server to get its status.
We can stop recording packets and look at the IP in the Recv packet. Its the first one at top and the IP on the right.
I have "174.142.118.237" you can ignore the :11091 as that is the Port.
We can then use cheat engine and search for this IP by searching for it as a string.
There are multiple places it is stored including one spot in memory where there is a large list of IP's.
Theres one for each server whilst we may want to modify this in the future as to have multiple servers we will just use the first green one for now.
I have found it at 00560A28
We have to find a signature for it.
Now being a string value we can just use that as the signiture. Providing the IP does not change our code will work and that it is the #1 result when we search for it another option is to retrive the new ip or new address from a website.
Reading/Writing memory is slightly different for Strings.
A string is a char* a pointer to characters the string keeps going until there is a 00 this NULL Byte is like the FullStop. When the computer reads to this it stops reading the string.
C Provides some functions for us to read and write strings.
We can use sprintf or strcpy, or memcpy, or we can write our own way using a for loop and copying each char/byte.
I will use strcpy.
memcpy can be used to copy memory which can be handy for other situations involving structures or an array of byte data.
We also need to find World IP luckly it is close to the Login ServerIP the game assumes that all the world servers are in same IP range just a different last didget.
So we see a the same IP as Login IP but with a %s for the last didgets when we view the login ip address in memory view.
Our world IP is "174.142.118.%s" the game uses sprintf and replaces the last didget with one for whatever map server to connect to.
Because our pserver is going to be better designed than the current game server we will just overwrite this %s with our own didget.
So lets code it in.
TSXClient.h under DWORD CurrentZoomAddress = 0;
Code:
// Internet Addresses char* TSXLoginIP="127.0.0.1"; char* TSXWorldIP="127.0.0.1"; char* LoginIP=0; char* WorldIP=0;
Code:
// Get IP Addresses LoginIP = (char*)FindPattern(0x00560000,0x0056FFFF,(BYTE*)"174.142.118.237","xxxxxxxxxxxxxxx"); WorldIP = (char*)FindPattern(0x00560000,0x0056FFFF,(BYTE*)"174.142.118.%s","xxxxxxxxxxxxxx");
Code:
// Set IPs strcpy(LoginIP,TSXLoginIP); strcpy(WorldIP,TSXWorldIP);
We may wish to change this later to get an IP address from CMD Line, an INI file or a registry key.
I will probably cover using a CMD Line paramater or ini later.
Part 4 Exceptions, Input , Calling Game Functions
Exception Handler
In TSXClient.h add include for #include <sstream> under widows.h
Put this define at the bottom under FindPattern
Code:
static LONG WINAPI UnhandledExcepFilter(PEXCEPTION_POINTERS p);
Code:
SetUnhandledExceptionFilter(UnhandledExcepFilter);
Code:
static LONG WINAPI UnhandledExcepFilter(PEXCEPTION_POINTERS p)
{
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)p->ContextRecord->Eax);
stringstream Message;
// I want this replaced with a dialog box that can show registers and stack later :)
static char msg[256];
sprintf_s(msg, 256, "The game has crashed...\nUnhandled exception 0x%08X at 0x%08X\nExecuting Address: 0x%08X\nEIP: 0x%08X\n\nDo you want to continue execution? (Clicking cancel may cause application to terminate)",
p->ExceptionRecord->ExceptionCode,
p->ExceptionRecord->ExceptionAddress,
p->ContextRecord->Eip);
LPCONTEXT lpContext;
HANDLE hThread;
GetThreadContext(hThread,lpContext);
if (MessageBoxA(0, msg, "Unhandled Exception", MB_ICONERROR | MB_OKCANCEL | MB_DEFBUTTON1)==MB_OK)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
SEH can be used to alter memory that is protected by anti hack to bypass anti hacks and such.
Temporarly disabling IP change so we can connect to real server.
Just comment the lines in TSXClient.cpp
Code:
// Set IPs //strcpy(LoginIP,TSXLoginIP); //strcpy(WorldIP,TSXWorldIP);
Keyboad Input
There are two ways I can think of to get keyboard input.
One is to use GetAsyncKey or other api's to get key states.
Another is to hook the games WM_KEYDOWN event.
For now ill make a function to see if a key is pressed and later we can hook the WM_LEYDOWN or Keyup events.
Calling Game Functions
It is possible to call functions that the game uses.
As an example I will call the games SendChat function.
First of all we have to find it we can do this with ollydbg.
Get into world with ollydbg attached
Breakpoint Send
Unbreak and Continue for every breakpoint hit whilst running around or whatever.
When no more breakpoints are poping up, type a chat message in normal chat like Hello World
A breakpoint will be hit we want to step out of this function. an easy way to do this is to press Alt+F9 then F7 then scroll up.
The code looks like this for me
Code:
00521D3E |. 51 PUSH ECX ; /Arg1 00521D3F |. B9 A0FA5900 MOV ECX,0059FAA0 ; |Buffer to store packet at 00521D44 |. E8 C72FF0FF CALL 00424D10 ; \TwelveSk.00424D10
Type a new message you will see this message pointed to by ECX.
So now that we have found the ASM we are set to call it.
I will be demonstrating the __asm method.
Here is my code
Code:
DWORD SendChatFunctionAddresss=0;
void __cdecl Game_SendChat( const char* szText )
{
//00521D3E |. 51 PUSH ECX ; /Arg1
//00521D3F |. B9 A0FA5900 MOV ECX,0059FAA0 ; |Buffer to store packet at
//00521D44 |. E8 C72FF0FF CALL 00424D10 ; \TwelveSk.00424D10
if (SendChatFunctionAddress!=0)
{
char * Buffer = new char[70]; // Packet Size
__asm
{
push szText
mov ecx,Buffer
call SendChatFunctionAddress;
}
delete [] Buffer;
}
}
I set it to 0 because I want to use signiture finding to find the address.
Here is how to make a signiture
Here is my code for the signiture finding.
Put it below the IP changing lines
Code:
// Get game chat function
//SendChatFunctionAddresss = 0x00424D10;
SendChatFunctionAddress = FindPattern(0x00400000,0x007FFFFF, (PBYTE)"\x8D\x8D\x00\x00\x00\x00\x51\xB9\x00\x00\x00\x00\xE8\x00\x00\x00\x00\xE9\x00\x00\x00\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x83\xC4\x08\x85\xC0\x75\x27\x8B\x15\x00\x00\x00\x00","xx????xx????x????x????x????x????x????xxxxxxxxx????")+13;
// Read the address called and add location of call + 4 to get the real address
SendChatFunctionAddress = (*(DWORD*)SendChatFunctionAddress)+SendChatFunctionAddress+4;
Log->Write("Send chat function address %X",SendChatFunctionAddress);
Remember our while loop at the bottom?
Code:
// Do this loop forever (whilst this thread is running)
while(1)
{
// Operations can be done here such as things to do with timers or checks of game status.
Code:
if (GetAsyncKeyState(VK_END)!=0)
{
Game_SendChat("Hello World");
}
If you do not have a END key or cant find it *if your on a laptop* its most likely required that you press the Function key first.
You can use another VK Value or 'P' or some other character.
Don't use characters you might type whilst at loading screens or logging in or character select as you may accidently press it and lock your account. *like i did blah luckly it was only a temp ban for 5 min*
And it works!
You may think wow thats cool but what use is it?
Its only an example, we can call functions the game uses like displaying notices, opening or closing dialogs. Playing Music files, teleporting to maps prehaps. Any function the game has we can call.
I May be able to find Login and make an auto login bot using this.
Could also be usefull if you want to make a key to drop junk items from invintory, or to pickup items etc.
Now ive read that some people recomend putting push ad and pop ad above and below the block of asm you write in to call the game function in case the game messes with the registers in some way screwing out your dll's calling. For example if the code modified ESI i think it is or maybe its EIP it would stuff up and crash.
We don't have to worrie about that for this function.
Some recomended reading for learning more asm is this great writeup
written by GeriFound it on my friends site Darkhook.net
I will add some more game functions here later prehaps.
Game functions can also be detoured after you find them. You can call them from the detour as well depending how you have done it. I will be detouring a game function later to change the character select background depending on the selected character's faction
Finding and calling Game DisplayChatMessage
Load game in ollydbg and run
Login to world
Go where no one is and disable the faction,guild,party chats
type test1234 into normal chat
search for test1234 in memory you can do this by clicking M or pressing Alt+M then pressing Ctrl+B or right click search. In the ascii part type test1234
You will probably want to do next search Ctrl+L untill you can scroll up slightly and see [ your name ] because there is also the text stored above players heads, you dont want that area.
Keep scrolling up till you get to the top of the text stuff
you will see in the hex two intergers.
1 is to do with the current line of the chat you are viewing and one is the total amount of messages in chat. Or the message count.
Set a memory write breakpoint on write on the 2nd one its closest to the first line of chat.
You can do this by selecting the 4 bytes with left mouse drag and right clicking then clicking breakpoint then memory on write.
Type something into the chat again like Hello World
The breakpoint will be hit and you will be taken to it in the cpu window
Remove the breakpoint by right clicking the 4 bytes and clicking breakpoint then Remove memory breakpoint you may need to move the code window to find the Dump window that you were using.
Click back into cpu window and step out of the code your in you do this by using Ctrl+F9 *executes till return* then Ctrl+F7 *step into*
Scroll up slightly you will see this code
Code:
004132D4 . 8D95 00FCFFFF LEA EDX,DWORD PTR SS:[EBP-400] 004132DA . 52 PUSH EDX ; /Arg3 004132DB . A1 48816000 MOV EAX,DWORD PTR DS:[608148] ; | 004132E0 . 50 PUSH EAX ; |Arg2 => 00000001 004132E1 . 8D8D 10FCFFFF LEA ECX,DWORD PTR SS:[EBP-3F0] ; | 004132E7 . 51 PUSH ECX ; |Arg1 004132E8 . B9 604C2E01 MOV ECX,012E4C60 ; | 004132ED . E8 FE031100 CALL 005236F0 ; \TwelveSk.005236F0
Press F9 to continue
Go back to the game any write Hello World again
If you have taken too long like I have you will need to relogin but don't worrie you wont need to restart all of this searching your have found the function.
Okay now that our breakpoints hit we can work out what these arguments are
F8 to step over them and look at the registers window on the right the info window at bottom above hex window and below cpu window and the stack window
You can work out what things are by looking at what you typed in, your charname and guessing at the Color because ive done this before.
There is 1 thing thats not an argument thats important the ECX
MOV ECX,012E4C60
You will need to do this when you call the game function.
The call is our call to the function to show chat message
The function is at this address 005236F0
If you have looked at the bytes for opcodes before you may be confused as to why
FE031100 is not our address for the function its because its an address to Jump to from current address. The program counter gets the address in the call added to it, taking it to the correct address in data to execute code from. To work it out we read the address in the bytes add the lines address to it + 5 for the opcode length and we have the function address. Thats not important for now as we are just going to hard code this in.
Code:
004132D4 . 8D95 00FCFFFF LEA EDX,DWORD PTR SS:[EBP-400] 004132DA . 52 PUSH EDX ; /Arg3 CharName 004132DB . A1 48816000 MOV EAX,DWORD PTR DS:[608148] ; | 004132E0 . 50 PUSH EAX ; |Arg2 Color 004132E1 . 8D8D 10FCFFFF LEA ECX,DWORD PTR SS:[EBP-3F0] ; | 004132E7 . 51 PUSH ECX ; |Arg1 Message 004132E8 . B9 604C2E01 MOV ECX,012E4C60 ; | 004132ED . E8 FE031100 CALL 005236F0 ; \TwelveSk.005236F0
Code:
void DisplayChatMessage(char* CharName, unsigned int Color, char* Message )
{
DWORD GameDisplayChatMessage=0x005236F0;
__asm
{
MOV EAX,CharName // Move the address CharName points too into EAX
PUSH EAX // Push EAX onto stack
MOV EAX,Color
PUSH EAX
MOV EAX, Message
PUSH EAX
MOV ECX,0x012E4C60 // Move that pointer to whatever it is into ECX we have to put 0x so VS can understand it as hex
CALL GameDisplayChatMessage // Call the game function
}
}
if (GetAsyncKeyState(VK_END)!=0)
{
DisplayChatMessage("MegaByte",1,"[MegaByte] Supness");
DisplayChatMessage("Test",3,"[Test] Test");
}
I call the function twice because I wanna make sure it can be run more than once without crashing.
Restart the game in ollydbg and inject your dll
Login to world etc
In ollydbg press Ctrl+G in code window and goto the function 005236F0 put a breakpoint on it
Also you may want to goto your dll's function like I do. I use Ctrl+G MainThread then scroll down till i see my GetAsyncKeyState VK_END part and put a breakpoint just on the conditional jump its a JE SHORT for me.
Go back to game and press your key to trigger the code.
Your breakpoint should be hit and you can step through the code to see what happens in registers.
If there is no crash and registers and stack are similar as the first time you saw them in this function then good.
Remove breakpoints and F9 and test out your code a few times.
It works all good!
Try this code
Code:
char * Message = new char[61];
for (unsigned int i=0;i<45;i++)
{
sprintf(Message,"[Test] Color %u",i);
DisplayChatMessage("",i,Message);
}
delete [] Message;
and the cheat engine help file for more info on registers and asm stuffI think ill get the notice game function now as I need break from assignment been doing stuff all day!
I left the game running all day by accident as I rushed too the bus as I forgot what day it was.
When I got home I saw the announcements in there "Check the TS2 Event Calendar"
So I search for it as text in cheat engine It may take a while to find a notice popup if you can find a time when theres lots of them coming through it is best.
I find it *at 012BE578* and right click it and browse.
I notice theres an array of strings for each notice the length of each message being 100 characters so 101 if you include the null terminator. There seems to be a limit of 8 notices in this array but I could be wrong.
Anyway grab the address of first char of first message.
Add a breakpoint in ollydbg on memory write
When breakpoint is hit remove the breakpoint
Step out untill you see a function call i think i stepped out four times or so and came across it here
Code:
0040718B . 50 PUSH EAX ; /Arg3 0040718C . 6A 00 PUSH 0 ; |Arg2 = 00000000 0040718E . 6A 00 PUSH 0 ; |Arg1 = 00000000 00407190 . B9 40E52B01 MOV ECX,012BE540 ; | 00407195 . E8 96280B00 CALL 004B9A30 ; \TwelveSk.004B9A30
MOV ECX,012BE540 is important its the address where things are written too or pointer to the class the game uses or something I think.
Argument 3 is our string pointer
Here is some pesudo code havent tested it will have to test it later
Code:
DWORD ShowNoticeFunctionAddresss=0;
DWORD NoticeBoardAddress=0;
void Game_ShowNotice( const char* szText,unsigned int Param2,unsigned int Param3 )
{
//0040718B . 50 PUSH EAX ; /Arg3
//0040718C . 6A 00 PUSH 0 ; |Arg2 = 00000000
//0040718E . 6A 00 PUSH 0 ; |Arg1 = 00000000
//00407190 . B9 40E52B01 MOV ECX,012BE540 ; |
//00407195 . E8 96280B00 CALL 004B9A30 ; \TwelveSk.004B9A30
if (ShowNoticeFunctionAddresss!=0)
{
char * Buffer = new char[101]; // Message length
strncpy(Buffer,szText,100);
__asm
{
push Buffer
push Param2
push Param3
mov ecx,NoticeBoardAddress
call ShowNoticeFunctionAddresss;
}
delete [] Buffer;
}
}
Game_ShowNotice("Test",1,10); is pretty cool sound effect
So theres some sound playing code in here depending on the state of notice we know this from when theres battle notices up there, faction leader, gm and generic ones.
I was able to find the code by breakpointing each call and seeing what ones where hit then nopping the push's and the call and observing what was going on. Putting back opcodes ide replaced with Alt+Backspace when needed.
[code]004B9B45 6A 64 PUSH 64
004B9B47 6A 00 PUSH 0
004B9B49 B9 F895FC00 MOV ECX,00FC95F8
004B9B4E E8 3DDEF7FF CALL 00437990[CODE]
What I think the arguments are so far.
Volume and panning?
I went into the function at 00437990 and breakpointed the top of it.
I then went into game and opened invintory and clicked on an item. The breakpoint was hit so it is sound playing function.
At the memory 00FC95F8 there is a file path to sound and other data. It appears to be a structure.
Ive worked out this much so far
struct GameSound
{
BOOL Loaded;
char File[100];
...
DWORD * Data; // Probably SomeGameSoundStruct*
...
float LastPlayed; ?
}
We know the game uses Direct Sound and some sort of OGG decoder.
So calling this game function with a certain ECX allows the playing of files that are already loaded. If we can learn more we should be able to completely load and play any sound we wish.
We could even make a detour for this sound playing function that casts ECX +4 to a char* and dumps out the file path every time a sound is played, allowing us to work out easier what sound files are for what game action. Once we know a files address in memory we can easily put a conditional breakpoint on the function when ECX == that address then we can step out and scroll up to find the game funcion for that event.
-- WORK IN PROGRESS!!! --
Not that we have the game function worked out we should be able to call it from our DLL and have notice messages show at the top of the screen. We must be careful not to put a string with length > 100 into it. In fact I will alter the function to make sure its safe.
Code:
char * Message = new char(101); strncpy( Message,szMessage,100 );
You will most likely have to relogin... as this will time out if your not fast.
Changing how EXP is displayed
Well we know about how sprintf works and if you dont you should google it
The strings are as follows for HP and CHI
HP is "%d/%d"
CHI is "%d/%d"
EXP is "%.3f" which means float to .3 precision
I wanna change it to "%.2f%%" or a custom string of some kind from ini later prehaps.
So I search for all referenced text strings and find the ones I want it takes a while but I notice they are under my char name. There sure is alot of them I found it by realizing that exp is %.3f and also by nopping the call after the refrence to this string.
Turns out %.2f%% is the only string like that in the game WOW!
Code:
00517BA6 |. 68 CCC85600 PUSH 0056C8CC ; ASCII "%.3f" 00517BAB |. 8D85 E8F7FFFF LEA EAX,DWORD PTR SS:[EBP-818] 00517BB1 |. 50 PUSH EAX 00517BB2 |. E8 8F690300 CALL 0054E546 00517BB7 |. 83C4 10 ADD ESP,10 00517BBA |. 6A 01 PUSH 1 ; /Arg4 = 00000001 00517BBC |. 8B8D 80F7FFFF MOV ECX,DWORD PTR SS:[EBP-880] ; | 00517BC2 |. 8B51 04 MOV EDX,DWORD PTR DS:[ECX+4] ; | 00517BC5 |. 83C2 12 ADD EDX,12 ; | 00517BC8 |. 52 PUSH EDX ; |Arg3 00517BC9 |. 8B85 80F7FFFF MOV EAX,DWORD PTR SS:[EBP-880] ; | 00517BCF |. 8B30 MOV ESI,DWORD PTR DS:[EAX] ; | 00517BD1 |. 81C6 89000000 ADD ESI,89 ; | 00517BD7 |. 8D8D E8F7FFFF LEA ECX,DWORD PTR SS:[EBP-818] ; | 00517BDD |. 51 PUSH ECX ; |/Arg1 00517BDE |. B9 10AF1801 MOV ECX,0118AF10 ; || 00517BE3 |. E8 B82CF6FF CALL 0047A8A0 ; |\TwelveSk.0047A8A0 00517BE8 |. 0FB7D0 MOVZX EDX,AX ; | 00517BEB |. 2BF2 SUB ESI,EDX ; | 00517BED |. 56 PUSH ESI ; |Arg2 00517BEE |. 8D85 E8F7FFFF LEA EAX,DWORD PTR SS:[EBP-818] ; | 00517BF4 |. 50 PUSH EAX ; |Arg1 00517BF5 |. B9 10AF1801 MOV ECX,0118AF10 ; | 00517BFA |. E8 C12CF6FF CALL 0047A8C0 ; \TwelveSk.0047A8C0 // NOP THIS to have no EXP value shown.
%.2f%% with the last byte being 00 to do this view it in hex select the bytes we want to modify and press 5 it will open the binary edit just click in the ASCII part up top and type in our string. hit enter and bam! change has been made if you crash you typed it wrong. causing problems with paramater/buffer managment.
And heres an example if EXP with a precision of 2 decimal points and a % sign
In our own dll we would want to use our own memory prehaps.
A bit about the font rendering code, one of the paramaters is a string one is the color value and one is length of string? I found out all the color values in another thread ill talk about it more later.

Multi Client
This one is something a lot of people want, heres how to do it.
First of all we need to run the game before we run it in ollydbg.
We then run the game in ollydbg so that we can see the message box text that pops up saying the window is already open. When the messagebox pops up we take note of the text and search for it as a referenced string find where its referenced scroll up and patch the conditonal jump.
We can find alot of strings actually like the same format. They are all initilization errors. Which we can look at the code around them to find out alot more about the game. Patching it to allow custom items database is along these lines of the item init error. Anyway
Double click [Error::FindWindow()] in the referenced text strings list in ollydbg
We see this code
Code:
00403ABE |. 85C0 TEST EAX,EAX 00403AC0 |. 74 3D JE SHORT 00403AFF 00403AC2 |. 68 00100000 PUSH 1000 00403AC7 |. 68 B4065600 PUSH 005606B4 ; ASCII "TwelveSky2" 00403ACC |. 68 DC075600 PUSH 005607DC ; ASCII "[Error::FindWindow()]" 00403AD1 |. 53 PUSH EBX 00403AD2 |. E8 82C6B002 CALL 02F10159
The code will now jump down under that area. Note we cant use this on this run of the game because the messagebox has already been called and the game will terminate. but what we can do is copy the address above our jump and set a hardware breakpoint on it the next run will hit it and we can make our change to see if it works. The DLL can apply the patch for us later when we code it in.
To make the HWBP right click the TEST EAX,EAX line and click Breakpoint then Hardware, On Execution
Stop the game and restart it keeping the first instance running.
Make the patch in ollydbg when breakpoint is hit then continue running with F9
You can remove the HWBP after patching if you want by clicking Debug Breakpoints and remove or right clickign the line and removing the breakpoint in the breakpoints menu.
SUCCESS two windows of the game.
So we now have to make a signiture for this and apply the patch in our dll.
I will do this later.
DisplayMessage function finding
The idea is to find the function for putting messages into chat window without sending the chat to server. This function is called when a chat packet is received from server. We can send normal chat packet and search for the text shown in the chat window. We have right address when we can modify the text in chat window. Then we can scroll up and we see an integer that increases with each message. its -4 from the first message there is also another int -4 from that which is the line chat is on set both to 0 if you want to clear all text. Anyway the chat message count is at -4 from message. We set it to 0 and find what writes to it by using a Memory breakpoint on write in OllyDBG we then step out till we see a function like this
Code:
004132C6 . /75 46 JNZ SHORT 0041330E 004132C8 . |0FB68D BBFBFF>MOVZX ECX,BYTE PTR SS:[EBP-445] 004132CF . |83F9 01 CMP ECX,1 004132D2 . |7D 20 JGE SHORT 004132F4 ; If user is GM or not 004132D4 . |8D95 00FCFFFF LEA EDX,DWORD PTR SS:[EBP-400] 004132DA . |52 PUSH EDX ; /Arg3 Name 004132DB . |A1 48816000 MOV EAX,DWORD PTR DS:[608148] ; | 004132E0 . |50 PUSH EAX ; |Arg2 Color 004132E1 . |8D8D 10FCFFFF LEA ECX,DWORD PTR SS:[EBP-3F0] ; | 004132E7 . |51 PUSH ECX ; |Arg1 Message 004132E8 . |B9 604C2E01 MOV ECX,012E4C60 ; | 004132ED . |E8 FE031100 CALL 005236F0 ; \Game_DisplayMessage 004132F2 . |EB 1A JMP SHORT 0041330E 004132F4 > |8D95 00FCFFFF LEA EDX,DWORD PTR SS:[EBP-400] ; Same thing below but for GM have color 2 004132FA . |52 PUSH EDX ; /Arg3 = 0018F3E0 004132FB . |6A 02 PUSH 2 ; |Arg2 = 00000002 004132FD . |8D85 10FCFFFF LEA EAX,DWORD PTR SS:[EBP-3F0] ; | 00413303 . |50 PUSH EAX ; |Arg1 00413304 . |B9 604C2E01 MOV ECX,012E4C60 ; | 00413309 . |E8 E2031100 CALL 005236F0 ; \TwelveSk.005236F0
Now lets find the announcment/warning/error/info window above chat window.
Again find its string that changes in memory for the address.
From memory above the messages ive found some interesting things such as the check box's around chat, if chat window is visible or not and mini map zoom. Worthy of noting for future endevours There is also the same sort of system as chat messages. So do the same thing. If your out of town press T to open shop or try to and trigger the error message.
After stepping out once ive found it
Code:
0046DA55 |. 50 PUSH EAX ; /Arg2 = 0000000F 0046DA56 |. 6A 46 PUSH 46 ; |/Arg1 = 00000046 0046DA58 |. B9 C8AC5B00 MOV ECX,005BACC8 ; || 0046DA5D |. E8 AEE8FBFF CALL 0042C310 ; |\TwelveSk.0042C310 0046DA62 |. 50 PUSH EAX ; |Arg1 0046DA63 |. B9 604C2E01 MOV ECX,012E4C60 ; | 0046DA68 |. E8 035B0B00 CALL 00523570 ; \TwelveSk.00523570
So the function we want is the bottom one
Game_DisplayInformationMessage( char * szMessage, int Color );
The function being passed as a paramater *gets the infomation message from a ID*
char * Game_GetInformationMessage( int ID )
Thats two functions worked out. how awesome is that.
You can take a look at the font colors in my other thread about them

Can call them the same as the other functions apart from the GetInformationMessage it returns a char* so we could possibly use a typedef im not sure will have to play around with it.
Either way it returns value in EAX so we can just copy the contents of EAX into a char* ReturnMe and be done with it. like so MOV ReturnMe,EAX
Infact I actually found another part of code we can detour. The code I found is responsiable for showing the disconnected message. We could detour this and make it log the character out. We know the logout button changes the screen value so we can find the screen value bp its write then click logout then step out a few times and should be at function to call. Altenativaly we might be able to just set screen to 1
Theres also this bit that I think checks if disconnected
Code:
0046DA41 |. 52 PUSH EDX ; /Arg1 => 00000000 0046DA42 |. B9 A0FA5900 MOV ECX,0059FAA0 ; | 0046DA47 |. E8 1449FBFF CALL 00422360 ; \TwelveSk.00422360 0046DA4C |. 85C0 TEST EAX,EAX 0046DA4E |. 75 32 JNZ SHORT 0046DA82
How to find the GUI clicking function
You can find the Battle Mode costume thing by searching for a 0 when normal town clothes then click it once search for 1 click it again search for 2 click it again search for 3 click it again search for 0 etc.
Once thats found we can find the code that changes it.
Code:
00516526 |. /E9 59080000 JMP 00516D84 0051652B |> |8B0D D4215A00 MOV ECX,DWORD PTR DS:[5A21D4] // Gets current value 00516531 |. |83C1 01 ADD ECX,1 // adds one 00516534 |. |890D D4215A00 MOV DWORD PTR DS:[5A21D4],ECX // Stores the value 0051653A |. |833D D4215A00>CMP DWORD PTR DS:[5A21D4],3 // Checks if the value is > 3 00516541 |. |7E 0A JLE SHORT 0051654D 00516543 |. |C705 D4215A00>MOV DWORD PTR DS:[5A21D4],0 // If it is set value to 0
Code:
004B827C |> \8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C] 004B827F |. 51 PUSH ECX ; /Arg2 004B8280 |. 8B55 08 MOV EDX,DWORD PTR SS:[EBP+8] ; | 004B8283 |. 52 PUSH EDX ; |Arg1 004B8284 |. B9 604C2E01 MOV ECX,012E4C60 ; | 004B8289 |. E8 42D80500 CALL 00515AD0 ; \TwelveSk.00515AD0
We can now emulate clicking gui events.
If we look around theres actually other function calls around this one for other clicking events.
Theres 40 or so and they appear to be in a nested if statement structure. Wow. if it returns 1 then clicks been processed. 0 otherwise.
What would we want this for you may ask? well we want to improve the GUI later so being able to click on our GUI stuff would be a great feature if we detour the code of that function we can do it.
Rather than using things like GetKeyAsyncState or other things to get input from user we can make use of game functions which are already setup to listen for keyboard and mouse input from the user in the game window
Theres going to be functions like this for right click,left click down left click release and mouse wheel and keypresses and all sorts.
Something else interesting is, the Fighting Model option we are switching between 0 and 3 with that button is in the Esc menu options. Around the memory area of Fighting Model is all these other options values. They are saved into the settings file when game closes correctly I think. Altering these values lets us do things like turn on auto deny requests instantly for the user when enabling certian functions. Or we can disable brightness/particle effects when in cave to make things even darker.
Lets find the Screen Value its usefull to tell what screen the game is on.
1=Loading Game
2=Server Select
3=Login
4=Character Select
5=Loading Zone
6=World
I believe there is ability to have other screens too that are hidden like game credits.
We find it by using 4byte search and scanning the appropriate value at certian screens.
We can find out alot with this screen value rather than just using it for checking things we can also bp it on write and find code that change maps or logs in prehaps or logs out of course we would need to find MapID for this too.
By breaking it at loging in and scrolling up in the code window after continuing from the breakpoint we can see that the Login sequence is not async its completly syncrynous thats why the game can appear to go not responding if it cant contact the server very fast. And also why whilst developing pserver without knowing packets or screwing them up it locks and waits.
I belive this is the function for loging in although we probably cant just call it due to the game being all singular threading here. There will be memory options to set which would trigger it, if we find those we can do a auto login with just memory writes.
Code:
004619C8 |. 52 PUSH EDX ; /Arg4 004619C9 |. 68 915F0100 PUSH 15F91 ; |Arg3 = 00015F91 004619CE |. 68 B4E91501 PUSH 0115E9B4 ; |Arg2 = 0115E9B4 ASCII "USERNAME" 004619D3 |. 68 A3E91501 PUSH 0115E9A3 ; |Arg1 = 0115E9A3 ASCII "PASSWORD" 004619D8 |. 8B4D E4 MOV ECX,DWORD PTR SS:[EBP-1C] ; | 004619DB |. E8 600D0000 CALL 00462740 ; \TwelveSk.00462740
We will be wanting a signiture to Screen Value as it is great for checking things.
Finding main game draw/render method
Put memory breakpoint on access on Kobold text mouse over kobold step out a few times theres alot of calls its the loop.
It even checks screen value
Code:
0047069A |. 83BD 40FBFFFF>CMP DWORD PTR SS:[EBP-4C0],6 ; IF ScreenValue==6 or Is in world 004706A1 0F87 D0010000 JA 00470877 ; Then Process rendering of below
How to enable loading of custom items
Same as the multi client patch find the error for invalid items "[Error::mBADWORD.Init()]" and patch the conditional jump above it.
Code:
00402F02 |. /75 30 JNZ SHORT 00402F34 00402F04 |. |68 00100000 PUSH 1000 00402F09 |. |68 B4065600 PUSH 005606B4 ; ASCII "TwelveSky2" 00402F0E |. |68 24055600 PUSH 00560524 ; ASCII "[Error::mITEM.Init()]"
Code:
00402F02 /EB 30 JMP SHORT 00402F34 ...etc
We can step into most of them to see what files they open
ApplicationInit() Initilizes Application
FindWindow() Checks if game window already open
mBADWORD.Init() Loads swear filter
mFONTCOLOR.Init() Initilizes the font color array
mGDATA.Init() Loads game data or checks folders
mINPUT.Init() Sets up input devices
mITEM.Init() Loads item db
mLEVEL.Init() Load level db
mMAIN.Init() Load main menu?
mMESSAGE.Init() Load messages
mMONSTER.Init() Load monster info
mMYINFO.Init() Load my info?
mNETWORK.Init() Initilize network WSAStart etc
mNPC.Init() Load NPC
mPAT.Init() Load ?
mPLAY.Init() Load ?
mQUEST.Init() Load Quests
mSKILL.Init() Load Skills
mTRANSFER.Init()
mUI.Init() Load UI
mUTIL.Init()
mWORKER.Init() Loadup worker threads
mZONEMAININFO.Init() Initilize Zone Info
mZONEMOVEINFO.Init() Initilize Zone moving
mZONENAME.Init() Initilize Zone Names
mZONENPCINFO.Init() Initilize Zone NPC info
ReadyForDrawing() Set engine into ready for rendering/drawing stage render loops under this
Some test/devel server names and ips
[INSIDE_US_TEST_WORLD]
[Server_Integration_TEST_WORLD]
[TEST_WORLD]
0.0.0.0
125.61.95.200
174.142.118.%s
174.142.118.229
174.142.118.237
Commands
[SYSTEM UNKNOWN_COMMAND]
GM Command Error
GM Command OK!!!
Language
US
Since the whole game engine is bassed on G3D Engine we can download that and get even more of an idea on the behind the scenes code. From previous looking at the code ive found that the resolution and keyboard and mouse buttons are all close. From looking at the G3D Engine code Win32Window.h I can see this among other data
Code:
bool m_mouseVisible;
bool m_inputCapture;
/** Mouse Button State Array: false - up, true - down
[0] - left, [1] - middle, [2] - right, [3] - X1, [4] - X2 */
bool m_mouseButtons[8];
bool m_keyboardButtons[256];
mutable _DirectInput* m_diDevices;
G3D::Set< int > m_usedIcons;
/** Coordinates of the client area in screen coordinates */
int m_clientX;
int m_clientY;
The function for managing user input reads the values from these arrays so we can write straight to the arrays to simulate 3D input. Win!
If we get around too hooking Dinput8 keyboard stuff later the keyboard codes are in the DIK_ format which is in directinput8.h in dxsdk.
Finding input lets find the invintory address its a 0 when its closed and 1 when its open 4 byte. We open and close it with I.
I found it at 012D1FD4 so i memory write bp it then press I and remove breakpoint and Step out Step Out
and we find the function to manage if I is pressed to open invintory.
Here we will use a trick of seeing if conditional jumps are taken or not when pressing i and then again pressing o and comparing.
Breakpoint goto game with mouse and press I and step through for each breakpoint press the comment key ; and type t if its taken and n if not taken.
Do this till our call to change invintory open to 1.
Now run and redo with a keypress of O we see that the first conditional jump
Code:
004F6EC3 |. /0F85 96000000 JNZ 004F6F5F ; n
Above that jnz we have
Code:
004F6EB0 /$ 55 PUSH EBP 004F6EB1 |. 8BEC MOV EBP,ESP 004F6EB3 |. 83EC 10 SUB ESP,10 004F6EB6 |. 894D F8 MOV DWORD PTR SS:[EBP-8],ECX 004F6EB9 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 004F6EBC |. 83B8 DC010000>CMP DWORD PTR DS:[EAX+1DC],0
From directinput8.h we know the key value of I and O
Code:
const uint32 DIK_I = 0x17;
Okay looks like im wrong turns out that CMP is checking if invintory is open or not.
The address EAX+1DC is the invintory open address damn. but thinking about it makes sense
If invintory is not open check to see if key is I otherwise jump out because this function only handles opening invintory. *hate the coding though*
If we continue my trick down with invintory closed but pressing o we get to this
Code:
004F6F2F |. 8B88 20685700 MOV ECX,DWORD PTR DS:[EAX+576820] ; Address points to keypress 004F6F35 |. 894D F4 MOV DWORD PTR SS:[EBP-C],ECX ; Copy the keypress into EBP-C 004F6F38 837D F4 17 CMP DWORD PTR SS:[EBP-C],17 ; See if key I is pressed 004F6F3C |. 74 02 JE SHORT 004F6F40 ; t n 004F6F3E |. EB 1B JMP SHORT 004F6F5B 004F6F40 6A 00 PUSH 0 ; Turn on invintory
Here is my Dinput8 Keyboard hook in a CE Auto Assembler script. the key code and state end up in appropriate memory locations. This could be made into C++ easy enough I may do this later.
Code:
// Made by MegaByte alloc(newmem,100) //bytes label(returnhere) label(originalcode) label(exit) alloc(KeyCode,4) alloc(KeyState,4) //alloc(KeyPointer,4) // There is an Uknown data // And Total Keys Pressed newmem: // Record Key Event Pointer //mov [KeyPointer],EAX // Record KeyCode mov [KeyCode],EDI // Record Key State mov EDI,[EAX+4] mov [KeyState],EDI // Continue MOV EDI,[EAX] MOV EBX,[EBX+24] MOV EDI,[EBX+EDI*8+4] // Record Key State //mov [KeyState],EDI // Continue MOV [ECX+10],EDI MOV EDI,[EAX+4] MOV [ECX+4],EDI MOV EDI,[EAX+8] MOV [ECX+8],EDI // Record Key Event Pointer //mov [KeyPointer],EDI // Continue MOV EDI,[EAX+C] MOV [ECX+C],EDI originalcode: exit: jmp "DINPUT8.dll"+6B77 "DINPUT8.dll"+6B59: jmp newmem returnhere:
Theres still another keyboard buffer we want with all the keys in it and we can write into to simulate keyboard input. Its seeming difficult to find.
if we step out from that code a few times we get back too TwelveSky2 memory and we are in a function that is responsible for getting calling it.
Im gonna take a break on keyboard input buffer finding for now.
Using an INI
Well I have coded an ini class for my projects. Whilst it is not complete it should work for what we want.
Create CIni.h with this code
Code:
#ifndef __TSX_INI__H
#define __TSX_INI__H
#include <map>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
using namespace std;
class Ini_Section
{
private:
string Name;
map<string,string> Options;
public:
Ini_Section();
Ini_Section(string _Name)
{
SetName(_Name);
};
~Ini_Section()
{
Clear();
};
void SetName(string _Name)
{
Name=_Name;
}
string GetName()
{
return Name;
}
void Add(string Key,string Value)
{
//Options.insert(Key,Value);
Options[Key]=Value;
};
void Add(string Key,int Value)
{
stringstream MyStream;
MyStream << Value;
Options[Key]=MyStream.str();
//Options.insert(Key,MyStream.str());
};
string GetString(string Key,string Default="")
{
map<string,string>::iterator it;
if (Options.count(Key)>0)
{
it = Options.find(Key);
}
else
{
// Comment to remove adding if the key does not exist
Add(Key,Default);
return Default;
}
return it->second;
};
unsigned int GetInt(string Key,int Default=0)
{
map<string,string>::iterator it;
if (Options.count(Key)>0)
{
it = Options.find(Key);
}
else
{
// Comment to remove adding if the key does not exist
Add(Key,Default);
return Default;
}
return atoi(it->second.c_str());
};
void Remove(string Key)
{
map<string,string>::iterator it = Options.find(Key);
Options.erase(it);
};
void Clear()
{
Options.clear();
};
string Save()
{
stringstream Data;
Data << "[" << Name << "]\r\n";
for (map<string,string>::iterator it = Options.begin();it!=Options.end();it++)
{
Data << it->first << "=" << it->second << "\r\n";
}
Data << "\r\n";
return Data.str();
};
};
class Ini
{
private:
vector<Ini_Section*> Sections;
string FileName;
Ini_Section* Section;
bool Changed;
public:
Ini()
{
Section=0;
Changed=false;
};
Ini(string _FileName)
{
Section=0;
SetFileName(_FileName);
Parse();
Changed=false;
};
~Ini()
{
Clear();
};
void SetFileName(string _FileName)
{
FileName=_FileName;
};
void Clear()
{
for (vector<Ini_Section*>::iterator it=Sections.begin();it!=Sections.end();it++)
{
delete (*it);
}
Sections.clear();
};
void Parse()
{
Clear();
// Open the file and parse it and all that
fstream File;
File.open(FileName.c_str(),ios_base::in);
if (!File.is_open())
{
// throw an exception that file is not open
return;
}
// Get file size
// File.seekg(0,ios::end);
// size_t FileSize=File.tellg();
// File.seekg(0,ios::beg);
char Line[255];
// Remove the \r if its there.
while (!File.eof())
{
File.getline(Line,255,'\n');
if (Line[strlen(Line)]=='\r')
{
Line[strlen(Line)]=0x00;
}
if (File.fail())
{
// throw exception for invalid size data maybe?
// Can increase the size of Line to compensate not that an ini file should need more than 255 chars for a option.
}
if (Line[0]=='[')
{
// Its a section
string SectionName=Line;
SectionName = SectionName.substr(1);
SectionName = SectionName.substr(0,SectionName.length()-2);
Add(SectionName);
}
else if (Line[0]==';')
{
// Comment just ignore...
// Im not going to bother with preserving or saving comments back into ini files.
// As I don't think comments should be left in release ini files.
}
else
{
// Its either a key value or a blank line
// Check for =
string TheLine=Line;
size_t Seperator = TheLine.find("=");
if (Seperator!=string::npos)
{
// = found in TheLine
string Key=TheLine.substr(0,Seperator);
string Value=TheLine.substr(Seperator+1);
Add(Key,Value);
}
else
{
// = was not found so will consider this line blank.
}
}
}
File.close();
};
void Save()
{
fstream File;
File.open(FileName.c_str(),ios_base::out);
if (!File.is_open())
{
// throw an exception that file is not open
}
// Iterate through all sections saving them to the file
File << "; Generated by Liam Mitchell's ini class\r\n";
for (vector<Ini_Section*>::iterator it=Sections.begin();it!=Sections.end();it++)
{
//output this to the file
string Data = (*it)->Save();
File << Data.c_str();
}
File.close();
};
void SetSection(string SectionName)
{
Section=0;
for (vector<Ini_Section*>::iterator it=Sections.begin();it!=Sections.end();it++)
{
if ((*it)->GetName()==SectionName)
{
Section=*it;
break;
}
}
if (Section==0)
{
// Comment out to remove adding new section if it does not exist.
// Add new section
Add(SectionName);
}
};
string GetString(string Key,string Default="")
{
if (Section==0)
{
Add("Default");
}
return Section->GetString(Key,Default);
};
unsigned int GetInt(string Key,int Default=0)
{
if (Section==0)
{
Add("Default");
}
return Section->GetInt(Key,Default);
};
void Add(string Key,string Value)
{
if (Changed==false)
{
Changed=true;
}
if (Section==0)
{
Add("Default");
}
return Section->Add(Key,Value);
}
void Add(string Key,int Value)
{
if (Changed==false)
{
Changed=true;
}
if (Section==0)
{
Add("Default");
}
return Section->Add(Key,Value);
}
void Add(string SectionName)
{
if (Changed==false)
{
Changed=true;
}
Ini_Section* NewSection = new Ini_Section(SectionName);
Sections.push_back(NewSection);
SetSection(SectionName);
}
bool GetChanged()
{
return Changed;
}
};
#endif
Then you can access keys from it like so
string Cake = Config.GetString( "Cake","Cake" );
unsigned int Score = Config.GetInt( "Score",0 );
The first string in the parameters is the key and the second parameter is the default value.
I may have to work on making this config class a singleton later although it would probably be easier to make an inherited class for the purpouse of a global config ini and make that a singleton.
We can replace our IP changing code to use the ini for varables.
TSXClient.ini
Code:
[TSXClient] OrigionalLoginIP=174.142.118.237 OrigionalWorldIP=174.142.118.%s NewLoginIP=127.0.0.1 NewWorldIP=127.0.0.1
RANDOM STUFF TO WORK ON
Code:
I can find WM_MOUSEMOVE and other events by right clicking in cpu window of olly finding all intermodular calls of TwelveSky2.exe then double clicking Destination then double clicking "Case 202 (WM_LBUTTONUP)" and scrolling around a bit. So what do we know about windows event handling? Here is a link to read up on about [URL="http://msdn.microsoft.com/en-us/library/ms633573(v=vs.85).aspx"]WindowProc Callback Function[/URL] We can see the callback function is defined as such [CODE]LRESULT CALLBACK WindowProc( __in HWND hwnd, __in UINT uMsg, __in WPARAM wParam, __in LPARAM lParam );
uMsg being the message for example WM_MOUSEMOVE
and wParam and lParam being paramaters.
Here is what the switch case part looks like. hwnds already been handled hence why we are in this event manager. and uMsg has been handled by the switch case. that leaves X and Y
Code:
004028BF > \8B4424 10 MOV EAX,DWORD PTR SS:[ESP+10] ; Case 200 (WM_MOUSEMOVE) of switch 00402814 004028C3 . 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+C] 004028C7 . 8BD0 MOV EDX,EAX 004028C9 . 51 PUSH ECX ; /Arg3 004028CA . C1EA 10 SHR EDX,10 ; | 004028CD . 0FB7C0 MOVZX EAX,AX ; | 004028D0 . 52 PUSH EDX ; |Arg2 004028D1 . 50 PUSH EAX ; |Arg1 004028D2 . B9 00E71501 MOV ECX,0115E700 ; | 004028D7 . E8 C4160500 CALL 00453FA0 ; \TwelveSk.00453FA0
Arg 1 is X
Arg 2 is Y
Arg 3 is button state? I cant really tell because every time i move mouse into the game window it breakpoints and I CBF setting up a conditional breakpoint in olly.
CALL 00453FA0 is the function where all the mouse movement is handled think of it as
Game_MouseMoveEvent( int X, int Y, void* NoIdeaYet)
{
// DO checks to see if mouse is over something.
// Translate screen co-ords to world co-ords it sort of draws a line from the mouse cursor to the world to see what it hits, Called a trace I think. Anyway...
//If it is coloiding with a game object see what it is do something.
// After all the 3D checking check 2D stuff to see what we are over in the GUI element side of things.
}
* i was told its safe in alot of cases to use void* if you dont know what a type is in asm because asm dosnt care. its C++ that cares because they are there to help us manage data.
So lets find the call for hovering over a monster
Well im going to just do it blind!!! by nopping each call we come across and then putting it back.
This involves double clicking a CALL using Ctrl+C to copy the code then typing nop and hitting enter then putting it back after observing what is effected. You may think that this is going to take forever and well it just might take a while. So be patient. Its an effective way of finding out what calls do what.
First of all though so that you nop the right calls we are looking for ones like this
Code:
00453FD5 |. 51 PUSH ECX ; /Arg2 00453FD6 |. 8B55 08 MOV EDX,DWORD PTR SS:[EBP+8] ; | 00453FD9 |. 52 PUSH EDX ; |Arg1 00453FDA |. B9 E8362E01 MOV ECX,012E36E8 ; | 00453FDF |. E8 0CCC0B00 CALL 00510BF0 ; \TwelveSk.00510BF0
Finding every PacketID from tracing back from the showing of received chat packet function.
So from our receive chat function or Game_DisplayMessage we step out untill we find what calls it.
Ive steppd out twice and got to this code
Code:
00404874 |. 0FB655 FB |MOVZX EDX,BYTE PTR SS:[EBP-5] 00404878 |. FF1495 A0F259>|CALL DWORD PTR DS:[EDX*4+59F2A0] ; TwelveSk.00421300
Now to work out where the packet ids are stored. PacketID * 4 + 59F2A0
so A4 + 59F2A0 i think they are stored at 59F2A0 + the reason why we * by 4 is because size of addresses is 4 bytes. So the game has an array of function addresses. one for each packet id.
From this we can work out each and every packet ID and where it goes to in memory. I think I may have to write a script to dump this in a readable format. Basicaly put the function to manage the packet gets called. From looking at the memory at 59F2A0 and using hex editor to count the values where it looks appropriate. I belive that there are 145 unique packet ids with the first lot to 0x0B being for login server so thats why they are not in this array.
To write a function that saves the PacketID and FunctionAddress to file we need to read each 4 bytes.
Code:
void WriteWorldPacketIDFunctionCalls()
{
DWORD * PacketFunctionArray=(DWORD*)0x59F2A0;
Log->Write("Packet Function Array %X",PacketFunctionArray);
for (unsigned char i=0x00;i<0xFF;i++)
{
if ( PacketFunctionArray[i]!=0 )
{
Log->Write("%X->%X",i,PacketFunctionArray[i]);
}
}
}
And in our log file we now know every world packet past Init.
Code:
Packet Function Array 59F2A0 C->405350 D->405600 E->4056B0 F->405740 10->405C50 11->405F60 12->406C20 13->406F30 14->407150 15->4071C0 16->407200 17->407FB0 18->408040 19->4083C0 1A->4086B0 1B->40FE90 1C->410720 1D->410BE0 1E->410EF0 1F->4114B0 20->411A70 21->411BE0 22->411D50 23->411E60 24->411EA0 25->411FC0 26->412100 27->412800 28->4130E0 29->413220 2A->4133D0 2B->413740 2C->413850 2D->4138B0 2E->413B00 2F->413C30 30->413C80 31->413DE0 32->413F00 33->413FB0 34->4141A0 35->4142B0 36->414300 37->4144E0 38->414580 39->414600 3A->414670 3B->4147C0 3C->4148A0 3D->4148F0 3E->414AA0 3F->414B20 40->414BD0 41->414C20 42->414D00 43->414D50 44->414F10 45->414FC0 46->415020 47->415080 48->415160 49->4151B0 4A->415360 4B->415410 4C->415550 4D->415640 4E->415810 4F->4159E0 50->415C80 51->415E60 52->415F40 53->415F90 54->4160F0 55->416EB0 56->416F70 57->417040 58->417090 59->417160 5A->4176A0 5B->417750 5C->417860 5D->4179B0 5E->417B20 5F->417CC0 60->41FEF0 61->41FFF0 62->4203B0 63->420470 64->420480 65->4204C0 66->420600 67->420680 68->4206C0 69->420870 6A->4208F0 6B->420950 6C->420B30 6D->420C40 6E->420E60 6F->420300 70->421180 71->4211F0 72->421300 73->421380 74->421460 75->4217A0 78->421830 79->421910 7A->4219B0 7B->4213F0 7C->421AA0 7D->421C00 7E->421D60 7F->421E70 80->422170
Something else interesting is, we may now be able to call any packet function for received packets as if we received the packet, if we can find where the buffer and size of data are stored and if thats correct to the packet we want to emulate. We would have to step into a call and look at the registers and stack to see where the buffer and size are.
As we see packet id 29 calls function 413220 which if we look at it calls the function to write the chat message into the chat box. BOOYAH!
If we look at our chat function we can see the buffer is at 00586BFC
Code:
00413233 . C785 FCFBFFFF>MOV DWORD PTR SS:[EBP-404],00586BFC ; ASCII ")LKM2011"
If we put a breakpoint on the PUSH ECX for pushing the buffer to recv function we see its the same there. by looking at the recv function we can also bp size and see that our buffer has a size of 100000 bytes.
We could probably write the whole unencrypted chat recv packet into this buffer and call the function for it. However we would have to watch out for cross thread operations doing this. The Receive loop is 1 thread if we use another thread to access it we may curropt data thats in there during a receive operation. Causing a crash most likely. So its better to call the game functions.
We can also detour packet managing functions to our own code by altering this table providing we work out the function paramaters and type etc. We can hook the call's and modify data in the buffer for received packets before the game looks at it too. Say if the game sent a packet to the client saying its new health we could set its health to allways be max. so the game's health would never change. Whilst this may not work in ts2 it might work in other games.
There is space to add more packets if we can work it out. We could make our pserver send new packets to the client say if we wanted to play a sound file on everyones game, we could make a packet that sends its id and the id or name of sound file to play, then call the games sound file playing functions to play it. Knowing all the packet ids is helpfull because we can work out our progress on packet emulation for the server.
Finding packet ID from a memory value/function
A battle started so I entered and found Jinong score at 0116659C
I find what writes to it
I find the function of packet managment at 0041FEF0 *top address*
Look at table for packet ID by searching above value. *had to trim 0's from front*
PacketID is 60
Coming Up Next
- Changing character select background
- Improving game CPU Usage - requires a detour o.o!
- Showing enemy names above their heads - Fun to find a little detour prehaps or some cleaver patching.
- Option to allways show monster names above heads - This should be intersting to find!
- Pet Feeder (maybe) - Would just call game functions for using pills bassed on values it checks.
- GUI - Hooking D3D VTable and drawing stuff like I did in map fun!
- Adding full recovery to most town npc's as an option, of course patching it to prevent hacking ^_^
- Add droping silver to ground in?
- Remove keep alive packets or at least make wait time between them longer
Removing keep alive packets at Character Select
Login to server
Add breakpoint on all calls to send
BP on Data and run to see address of data
Remove bp
Goto Data in hex view
Put BP above encryption loop its right above send
Look at data see if 9th byte is 0C if it is keep alive sending has been found.
Stepout and you find this code
Code:
00462F60 |. 52 PUSH EDX ; /Arg1 00462F61 |. 8B4D C4 MOV ECX,DWORD PTR SS:[EBP-3C] ; | 00462F64 |. E8 87880000 CALL 0046B7F0 ; \TwelveSk.0046B7F0
Its sent every 30 iterations
Code:
00462F52 |. B9 1E000000 MOV ECX,1E 00462F57 |. F7F1 DIV ECX 00462F59 |. 85D2 TEST EDX,EDX
Keep alive in world is a packet id of 0D and has a size of 13 its sent every 4~5 sec as well. Can be found the same way.
Preventing game using so much CPU
We have to find the main loop and make it call a Sleep somewhere.
This code will make a sleep for 1ms
PUSH EAX
MOV EAX,00000001
CALL Sleep
POP EAX
To find the main game loop we can do it at login screen
Find referenced text string of [Error::AdjustWindowRect()]
Scroll down to bottom of the function
I think its a Do While loop anyway see the JMp at the bottom we can copy it
write our code and make it jmp to that then at the end of our code JMP to the top of game loop.
Keeping Whispers between zone changes
Maybe...
AutoHotKey has nicely provided us with a list of window messages

Changing Window Icon
We can use SendMessage and we need to define an icon in the resources.
// Set the window icon
HICON icon = (HICON)LoadImage(0,"tsx\\icon.ico",IMAGE_ICON,0,0, LR_LOADFROMFILE);
if (icon!=0)
{
SendMessage(WindowHandle,WM_SETICON,ICON_BIG,(LPAR AM)icon);
}
Auto Login using SendMessage
By using Spy++ ive been able to verify that all the Edit Box's in the game have Window Handles.
The handles are different each time the game runs of course, time to find where the window handles are stored in memory. We can search array of bytes and write the handles in backwards next to each other. And find them that way. We can also look at the games init function for the Editbox's
Find the reference to the error text "[Error::mEDITBOX::Init()]"
Scroll up a little bit you will see this code
Code:
00403298 |> \B9 70E71501 MOV ECX,0115E770 0040329D |. E8 3E2A0500 CALL 00455CE0 004032A2 |. 85C0 TEST EAX,EAX 004032A4 |. 75 30 JNZ SHORT 004032D6 004032A6 |. 68 00100000 PUSH 1000 004032AB |. 68 B4065600 PUSH 005606B4 ; ASCII "TwelveSky2" 004032B0 |. 68 9C035600 PUSH 0056039C ; ASCII "[Error::mEDITBOX.Init()]" 004032B5 |. 53 PUSH EBX 004032B6 |. E8 A2D1CF02 CALL 0310045D 004032BB |. 90 NOP 004032BC |. 5E POP ESI 004032BD |. 5D POP EBP 004032BE |. 33C0 XOR EAX,EAX 004032C0 |. 5B POP EBX 004032C1 |. 8B8C24 280400>MOV ECX,DWORD PTR SS:[ESP+428] 004032C8 |. 33CC XOR ECX,ESP 004032CA |. E8 E1AD1400 CALL 0054E0B0 004032CF |. 81C4 2C040000 ADD ESP,42C 004032D5 |. C3 RETN
The address 0115E770 is where our EditBox's handles are stored.
I have worked out the edit box's to be like this
Code:
num EDITBOX_VALUES
{
EDITBOX_NULL,
EDITBOX_USERNAME, //1
EDITBOX_PASSWORD, //2
EDITBOX_CHARACTER_NAME,
EDITBOX_WHISPER,
EDITBOX_CHAT,
//... Many more but I cant find them on low level char?
};
HWND * EDITBOXS = (HWND*)0x0115E770;
char * Username="MegaByte";
char * Password="qwerty1234";
To set the text in the Username and Password box's we can do this
Code:
SendMessage(EDITBOXS[EDITBOX_USERNAME],WM_SETTEXT,0,(LPARAM)Username); SendMessage(EDITBOXS[EDITBOX_PASSWORD],WM_SETTEXT,0,(LPARAM)Password);
Sending Mouse Left Press to game
Code:
void MouseLeftPress(DWORD X,DWORD Y)
{
LPARAM Position=MAKELPARAM(X,Y);
SendMessage(WindowHandle,WM_LBUTTONDOWN,MK_LBUTTON,Position);
SendMessage(WindowHandle,WM_LBUTTONUP,MK_LBUTTON,Position);
}
We should be able to read the X and Y of any game control * or work it out with maths
There may even be a call similar to Control::Click( X,Y ) or Control::MangeClick( Button,ControlID );
Setting Window icon
We want to change the icon as well as the text.
So under our previous code for setting window text lets put
Code:
// Set the window icon
HICON icon = (HICON)LoadImage(0,"tsx\\icon.ico",IMAGE_ICON,0,0,LR_LOADFROMFILE);
if (icon!=0)
{
SendMessage(WindowHandle,WM_SETICON,ICON_BIG,(LPARAM)icon);
}
For the Lulz!
Controling Winamp from the game
First of all we have to enumerate through the processes to find winamp.exe to get its ProcessID
Then we have to enumerate the processes windows and find the main window.
We can control winamp *every control or event* by telling windows to send an event message to the window. We can do this with the SendMessage API
Theres a nice post on winamp forums with everything we need to control winamp it can be found

You can do this with other players and software search the name of it then SendMessage. If there are no documented numbers / message values to send you can allways figure them out with OllyDBG and breakpoints or WinSpy++
Heres a function to get the song title from winamps window title.
Code:
char* GetWinampSongTitle()
{
// Code from winamp site
char this_title[2048],*p;
GetWindowText(hwndWinamp,this_title,sizeof(this_title));
p = this_title+strlen(this_title)-8;
while (p >= this_title)
{
if (!strnicmp(p,"- Winamp",8)) break;
p--;
}
if (p >= this_title) p--;
while (p >= this_title && *p == ' ') p--;
*++p=0;
}
return this_title;
}
Anyone who knows C++,reversing ce memory want to help out with pserver?
Anyone want to help with making the games SFX or Graphics better?






