Integrating AntTweakBar into Silkroad
I. Purpose
Finally! I have something fun and exciting to write about. With the previous two guides, you have seen how to locate Silkroad’s Direct3D objects as well as create a simple loader and injected DLL. This guide finally makes use of those two guides to show how to integrate AntTweakBar into Silkroad. While this guide will show you how to get your own simple GUI via AntTweakBar into Silkroad, it is still a “foundation” guide that is more knowledge driven rather than a practical implementation. This guide will get you started with having AntTweakBar available in Silkroad, but it does not make use of it yet for game specific reasons. That will come later in other guides.
II. Requirements
This guide might be a little advanced for most readers at this stage. I will try to make things as clear as possible and write towards an intermediate level, but some of the concepts used here just take time and experience to understand over a longer term.
In order to be able to follow along, you will need:
• OllyDbg 1.10 (or equivalent)
• Visual Studio 2008 (or equivalent)
• Microsoft’s DirectX March 2009 SDK (or equivalent)
• AntTweakBar (
, more instructions on this later)• Lots of time and patience
Before you continue this guide, you will need to have already read and understood the two previous guides:


In addition, you will need to read and follow my article
to have an understanding of the concepts I will be using in this guide. Do not rush through those resources as they are very important to comprehend for the long run. For an assembly reference, you should bookmark and learn to love
resource. That is the single link I refer people to when they have a question on ASM programming, so save it and get used to referring to it when you want to know about something in assembly.III. Theory
In the first guide, I explained how to locate Silkroad’s Direct3D objects. By now, you should know the game uses Direct3D to handle all of the visual aspects of the game. The game uses its own built in GUI that we cannot easily access or utilize at this point in time. If we wanted to add in our own custom GUI to the game, we are in a little bit of a bind. If we used a proxy DLL for Direct3D, we would be able to have access to all of the Direct3D functions, but we would have to also have all our logic in that DLL and have a more convoluted setup to work with.
By injecting our own DLL and hooking the Silkroad Direct3D functions, we can steal the pointers and other relevant information from the game and use that to our advantage. We can then develop our GUI and logic in the same DLL and not have to worry about any real problems of compatibility. The downside to the approach is that it is still quite a bit of work locating all of the functions we might use as explained in the first guide. However, the approach of hooking the Direct3D functions in the client is still the most ideal way to handle the problem so we will be sticking with that. Once we have the client’s Direct3D objects, we can then pass them to AntTweakBar to setup the custom GUI.
Here is a description of AntTweakBar taken from their home page:
AntTweakBar is pretty simple to use and setup, so it should fit our task well. The library itself is still a bit limited, but once you understand the concepts of integrating in your own GUI, then you can venture out into finding another library or even making your own to use instead!Quote:
AntTweakBar is a small and easy-to-use C/C++ library that allows programmers to quickly add a light and intuitive graphical user interface into graphic applications based on OpenGL, DirectX 9 or DirectX 10 to interactively tweak their parameters on-screen.
Program variables can be bound to graphical controls that allow users to modify them. Thus, parameters exposed by programmers can be easily modified. They are displayed into the graphical application through one or more embedded windows called tweak bars.
The AntTweakBar library mainly targets graphical applications that need a quick way to tune parameters (even in fullscreen mode) and see the result in real-time like 3D demos, games, prototypes, inline editors, debug facilities of weightier graphical applications, etc
The last bit of theory to understand for this guide is the art of creating codecaves. By now you should have read my Beginners Guide to Codecaves. Hopefully that served its purpose in helping you understand how they work and how to go about finding them. There still remains an element to the process that cannot be taught, only learned. If you are new to this stuff, you will not be able to easily accomplish what you want via codecaves and patching the client at first. It will be frustrating at times, but for all the time it takes to learn and build up experience is sweetly rewarded with the ability to do whatever you want in the client once you get the process down.
There are a lot of underlying rules and guidelines to coding at a low level which I cannot simply write out because you learn them and know them, but you just can’t recall them on demand. You have to assume that things simply do not work the way you think you might and try to make your code match the conventions shown in my examples if you try things on your own. The slightest differences in coding logic are the differences between a program that works and a program that doesn’t. Something as simple as declaring a variable inside a function and outside a function can have a great effect on the program at these levels, so make sure to be observant of every little detail in the presented code. Things are done the way they are shown for a very good reason!
With that said, we can now move into the specific code for this guide. Be sure to have a good understanding of the previously mentioned guides and resources before you continue!
IV. Implementation
Before we get started, we need to setup AntTweakBar with our project. Visit the official AntTweakBar
and grab the AntTweakBar_113-win.zip file. After you download and extract the files, you will want to create a new folder named AntTweakBar in the Common folder where common.h and common.cpp reside. Open the AntTweakBar folder and copy the contents of the lib and include folders of the AntTweakBar_113-win package here. Since AntTweakBar requires the DLL to be run in the same directory as the executable that uses it, you will also need to copy the AntTweakBar.dll file into your Silkroad directory alongside sro_client.exe! Here is a screenshot of the final AntTweakBar folder inside the Common folder for reference:
In order to integrate AntTweakBar into Silkroad, we have three tasks ahead of us. We must codecave the Direct3D CreateDevice function and steal its parameters. Then, we must codecave the Direct3D EndScene function. Finally, we need to codecave the client’s WndProc function and steal its parameters.
We know how to do two of those tasks as they are covered in a previous guide. However, I have not gone over explicitly how to locate and find the client’s WndProc function. In a sense I have, you just might not have realized it yet. If you think back to your Direct3D example program we coded and then reversed in Olly, you should notice how we setup the WndProc for our window via a variable in the window class object that was passed to RegisterClassEx. We can find the WndProc for the client the same way that we found our Direct3D functions!
We would need to find where the client creates the main window and then trace backwards a little for a call to RegisterClassEx around the area. I’m not going through the entire task because it would be a good exercise to see if you can find it yourself. If all else fails, you can always just set breakpoints in the User32.dll on the RegisterClassExA/CreateWindowExA functions to try and find it as well.
Now, we know everything we need to in order to accomplish our tasks. We will now go task by task until we get everything done. At the end of the guide, complete code will be posted, so do not worry if you are unsure of how things are supposed to be placed.
Let’s start out with the WndProc. If you are asking why we even need to bother with the WndProc, it’s because AntTweakBar needs the event messages to work correctly, so if we don’t do this, it simply will not work. Since we need to store all of the parameters from the WndProc call, we will first start out by declaring a structure to hold them:
Code:
// Holds the WndProc parameters. We group these fields together
// so we can easily rip them from the stack.
struct tSilkroadWndProc
{
HWND hWnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
};
Getting back on track, once we have our structure, we can make a global variable for it. We have to declare a global variable because we cannot use local variables in our codecave functions, as local variables directly modify the stack. Just under the structure definition, add in the global variable like so:
Code:
// Object that holds the WndProc function call parameters
tSilkroadWndProc SilkroadWndProc = {0};

We can verify that the function is the WndProc simply by searching for the function address, 0x7324F0, via the Search for -> Constant menu in Olly. We should find the address being moved into the stack and see a call to RegisterClassEx down a little. Through tracing the client and watching which windows are created when, we can verify this in game, which I have. Here’s a tip for being able to quickly find this location. Open any client from the past and search for the command CMP EAX, 0x496. You should only have one result. This location I first used back in 2007 and it’s been the same since, so you can use that to your advantage.
If you look at the WndProc from our Direct3D test program, or any Win32 program for that matter, you should know the function has 4 parameters. From the Codecave article, you should also know we need 5 bytes of space to setup a codecave, so you can see why we choose to codecave the line marked rather than the first instruction. Finally, there are a few comments on how to skip the current WndProc message. There is no easy way to explain the reasoning of how to figure that out other than trial and error and understanding how the function works. For now just take it on good faith that it is the right thing to do.
Now that we know what we need to codecave, we have to setup our codecave. Once again, from the Codecave article you should understand how a codecave function has to look and the basic operations it has to do. We have to declare the function in a special way, save the address that called it, save the registers, perform our custom operations, restore the registers, and then return to where we need to continue execution.
Here is the code for out codecave:
Code:
DWORD UserOnWndProc();
namespace CC_WndProc
{
DWORD codecave_WndProc_ReturnAddress = 0;
DWORD codecave_WndProc_esp = 0;
DWORD codecave_WndProc_result = 0;
void OnWndProc()
{
// 4 params, 4 bytes each = 16 total bytes to copy
memcpy(&SilkroadWndProc, ((LPBYTE)ULongToPtr(codecave_WndProc_esp)) + 4, 16);
// We want to keep higher level code separate for this section of low level code
codecave_WndProc_result = UserOnWndProc();
}
__declspec(naked) void codecave_WndProc()
{
// Save where we just came from
__asm pop codecave_WndProc_ReturnAddress
__asm mov codecave_WndProc_esp, esp
// Save registers before we call our function
__asm pushad
// User function to call before the EndScene function is called
OnWndProc();
__asm cmp codecave_WndProc_result, 0
__asm jne LABEL_1
// Restore registers
__asm popad
// Original client code (we do not pop esi since we never pushed it in the first place!)
__asm xor EAX, EAX
__asm ret 0x10
LABEL_1:
// Restore registers
__asm popad
// Original client code
__asm
{
CMP EAX, 0x496
}
// We need to return from where we came from now
__asm push codecave_WndProc_ReturnAddress
__asm ret
}
}
You will also notice we save the location of the stack into the codecave_WndProc_esp variable. Inside the OnWndProc function, we then simply perform a memcpy to copy the parameters from the stack into our object. This is the method I just realized we could use. It is far easier than having to copy things one by one, which is what I used to do. I offset the address of the stack by 4 since the return value of the WndProc invoker is on top, so we need to skip that.
Finally, at the end of the codecave we have some custom logic to figure out what to do. If the message is processed, we will want to return from the WndProc and not pass the message to the client. If we do not process the message or still want to pass the message to the client, we will then execute the regular client code we codecave and then return to where we were called from.
That’s all there is to our WndProc codecave logic. We still have to write out the codecave via a function call and implement the user function, but that will be shown later. Next, we can look at the Direct3D CreateDevice function call codecave. Since the CreateDevice function has parameters we wish to steal like the WndProc, we will create another structure to hold the data. This is what it looks like:
Code:
// Holds the Direct3D information passed to CreateDevice. We group these fields together so we can easily rip them from the stack.
struct tSilkroadD3D
{
IDirect3D9 * d3d;
UINT adapter;
D3DDEVTYPE deviceType;
HWND focusWindow;
DWORD behaviourFlags;
D3DPRESENT_PARAMETERS * presentationParameters;
IDirect3DDevice9 ** device;
};
Code:
// Object that holds our clients Direct3D information
tSilkroadD3D SilkroadD3D = {0};
Code:
void UserOnCreateDevice();
namespace CC_CreateDevice
{
DWORD codecave_CreateDevice_ReturnAddress = 0;
DWORD codecave_CreateDevice_esp = 0;
void StealCreateDeviceParameters()
{
// 7 params, 4 bytes each = 28 total bytes to copy
memcpy(&SilkroadD3D, UlongToPtr(codecave_CreateDevice_esp), 28);
}
void ProcessCreateDeviceParameters()
{
// We want to keep higher level code separate for this section of low level code
UserOnCreateDevice();
}
__declspec(naked) void codecave_CreateDevice()
{
// Save where we just came from
__asm pop codecave_CreateDevice_ReturnAddress
// Store the top of the stack so we can steal all the parameters in one go
__asm mov codecave_CreateDevice_esp, esp
// Save registers before we call our function
__asm pushad
// User function
StealCreateDeviceParameters();
// Restore registers
__asm popad
// Original client code
__asm
{
CALL NEAR EAX
MOV ECX,DWORD PTR SS:[EBP]
}
// Save registers before we call our function
__asm pushad
ProcessCreateDeviceParameters();
// Restore registers
__asm popad
// We need to return from where we came from now
__asm push codecave_CreateDevice_ReturnAddress
__asm ret
}
}

Since we need 5 bytes for the codecave and we wish to control the execution of the function, we will codecave the highlighted line as well as the one after it. That conveniently gives us our 5 bytes! Just like before, we store the address of esp, the top of the stack, so we can easily steal the function parameters. Once we have that information, we call the function ourselves and then process the results before we leave. We will implement our higher level user function UserOnCreateDevice later.
Last but not least, we need to implement the codecave for the Direct3D EndScene function. We need to hook this function because we want to be able to draw our GUI last on top of the screen right before the client ends drawing. Using the methods shown in the previous Direct3D guide, you should be able to find the EndScene function at the following location:

To get our 5 bytes, we will need to codecave the line before the function as well as the line after. No real problems here because the code to replace is simple and easily replicate in our codecave. Here is the relevant code for our codecave. This is by far the easiest function so far:
Code:
void UserOnEndScene();
namespace CC_EndScene
{
DWORD codecave_EndScene_ReturnAddress = 0;
void OnEndScene()
{
// We want to keep higher level code separate for this section of low level code
UserOnEndScene();
}
__declspec(naked) void codecave_EndScene()
{
// Save where we just came from
__asm pop codecave_EndScene_ReturnAddress
// Save registers before we call our function
__asm pushad
// User function to call before the EndScene function is called
OnEndScene();
// Restore registers
__asm popad
// Original client code
__asm
{
PUSH EAX
CALL NEAR EDX
MOV AL, 0x01
}
// We need to return from where we came from now
__asm push codecave_EndScene_ReturnAddress
__asm ret
}
}
This is what our function will look like now:
Code:
// The function where we place all our logic
void UserOnInject()
{
// Create a debugging console
edx::CreateConsole("SilkroadFramework Debugging Console");
// Mutex for the launcher, no patches required to start Silkroad now
CreateMutexA(0, 0, "Silkroad Online Launcher");
CreateMutexA(0, 0, "Ready");
edx::CreateCodeCave(0xA62FE8, 5, CC_CreateDevice::codecave_CreateDevice);
edx::CreateCodeCave(0xA5F05E, 5, CC_EndScene::codecave_EndScene);
edx::CreateCodeCave(0x7324F4, 5, CC_WndProc::codecave_WndProc);
}
As this point, we now have our codecaves in place and everything setup that we need to, barring the AntTweakBar specific code. That is what we will need to add in next. I will simply be using AntTweakBar’s tutorial code as well as a small modification to start things out. I would suggest taking a look over their
page before you continue!Here are the globals we will be using alongside AntTweak bar. At the end of the guide, I’ll post a complete listing at the DLL.cpp file so don’t worry if you do not know where to put it yet.
Code:
// Simple settings
int numSec = 100;
float color[] = { 1, 0, 0 };
unsigned int bgColor = D3DCOLOR_ARGB(255, 40, 255, 200);
// The main tweak bars we will use
TwBar * mainBar1 = 0;
TwBar * mainBar2 = 0;
// Flag to tell us if the library is initialized yet or not
bool bIsAntInitialized = false;
// Higher level functions called via AntTweakbar buttons
void TW_CALL RunSnow(void * clientData)
{
printf("RunSnow called!\n");
}
void TW_CALL RunSun(void * clientData)
{
printf("RunSun called!\n");
}
void TW_CALL RunRain(void * clientData)
{
printf("RunRain called!\n");
}
Code:
// User function called right after CreateDevice
void UserOnCreateDevice()
{
if(bIsAntInitialized == true)
{
TwTerminate();
bIsAntInitialized = false;
}
// Setup the Tweakbar
if(!TwInit(TW_DIRECT3D9, *SilkroadD3D.device))
{
MessageBoxA(0, TwGetLastError(), "Cannot initialize AntTweakBar", MB_OK | MB_ICONERROR);
}
else
{
// Mark this for our own reference
bIsAntInitialized = true;
// Create the first bar
mainBar1 = TwNewBar("edxLabs :: SilkroadFramework :: Bar 1");
TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar in a DirectX9 application.' "); // Message added to the help bar.
TwDefine(" TweakBar color='128 224 160' text=dark "); // Change TweakBar color and use dark text
TwAddVarRW(mainBar1, "NumSec", TW_TYPE_INT32, &numSec, " label='Strip length' min=1 max=1000 keyIncr=s keyDecr=S help='Number of segments of the strip.' ");
TwAddVarRW(mainBar1, "Color", TW_TYPE_COLOR3F, &color, " label='Strip color' ");
TwAddVarRW(mainBar1, "BgColor", TW_TYPE_COLOR32, &bgColor, " label='Background color' ");
TwAddVarRO(mainBar1, "Width", TW_TYPE_INT32, &SilkroadD3D.presentationParameters->BackBufferWidth, " label='wnd width' help='Current graphics window width.' ");
TwAddVarRO(mainBar1, "Height", TW_TYPE_INT32, &SilkroadD3D.presentationParameters->BackBufferHeight, " label='wnd height' help='Current graphics window height.' ");
// Create the second bar
mainBar2 = TwNewBar("edxLabs :: SilkroadFramework :: Bar 2");
TwAddButton(mainBar2, "buttonSnow", RunSnow, NULL, " label='Snow' ");
TwAddButton(mainBar2, "buttonSun", RunSun, NULL, " label='Sun' ");
TwAddButton(mainBar2, "buttonRain", RunRain, NULL, " label='Rain' ");
}
}
// User function called right before EndScene
void UserOnEndScene()
{
// Always be sure to check for these things
if(bIsAntInitialized)
{
TwDraw();
}
}
// User function called on the WndProc
DWORD UserOnWndProc()
{
// Do not let the TweakBar use the cursor since it conflicts with Silkroad's
if(SilkroadWndProc.message == WM_SETCURSOR)
{
// Return 0 to not pass the event to the client either.
return 0;
}
// Check to see if the event should be handled by the TweakBar
if(TwEventWin(SilkroadWndProc.hWnd, SilkroadWndProc.message, SilkroadWndProc.wParam, SilkroadWndProc.lParam))
{
return 0;
}
// The message was not handled and should be sent to the client
return 1;
}
Code:
#include <windows.h> #include "../common/common.h" #define TW_NO_LIB_PRAGMA #include "../common/AntTweakBar/AntTweakBar.h" #include <d3d9.h> #pragma comment(lib, "../common/AntTweakBar/AntTweakBar.lib")
One last thing of importance is I do not have any "cleanup" code integrated yet. Sometimes the client might not close properly (well this issue I'm seeing might be from Win7 as I've been having problems) so in the future we will need to add in some additional hooks to deinitialize AntTweakBar and anything else we might have. For now though, we just learning the basics.
Here are some example screenshots:


Here is the complete listing of DLL.cpp:
Code:
#include <windows.h>
#include "../common/common.h"
#define TW_NO_LIB_PRAGMA
#include "../common/AntTweakBar/AntTweakBar.h"
#include <d3d9.h>
#pragma comment(lib, "../common/AntTweakBar/AntTweakBar.lib")
// Global instance handle to this DLL
HMODULE gInstance = NULL;
// Function prototypes
void UserOnInject();
// Main DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
{
UNREFERENCED_PARAMETER(lpReserved);
if(ulReason == DLL_PROCESS_ATTACH)
{
gInstance = hModule;
// Do not notify this DLL of thread based events
DisableThreadLibraryCalls(hModule);
}
return TRUE;
}
// This is the main function that is called when the DLL is injected into the process
extern "C" __declspec(dllexport) void OnInject(DWORD address, LPDWORD bytes)
{
// Restore the original bytes at the OEP
DWORD wrote = 0;
WriteProcessMemory(GetCurrentProcess(), UlongToPtr(address), bytes, 6, &wrote);
// Call our user function to keep this function clean
UserOnInject();
}
//------------------------------------------------------------------------
// Holds the Direct3D information passed to CreateDevice. We group these fields together so we can easily rip them from the stack.
struct tSilkroadD3D
{
IDirect3D9 * d3d;
UINT adapter;
D3DDEVTYPE deviceType;
HWND focusWindow;
DWORD behaviourFlags;
D3DPRESENT_PARAMETERS * presentationParameters;
IDirect3DDevice9 ** device;
};
// Holds the WndProc parameters. We group these fields together so we can easily rip them from the stack.
struct tSilkroadWndProc
{
HWND hWnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
};
//------------------------------------------------------------------------
// Object that holds our clients Direct3D information
tSilkroadD3D SilkroadD3D = {0};
// Object that holds the WndProc function call parameters
tSilkroadWndProc SilkroadWndProc = {0};
// Simple settings
int numSec = 100;
float color[] = { 1, 0, 0 };
unsigned int bgColor = D3DCOLOR_ARGB(255, 40, 255, 200);
// The main tweak bars we will use
TwBar * mainBar1 = 0;
TwBar * mainBar2 = 0;
// Flag to tell us if the library is initialized yet or not
bool bIsAntInitialized = false;
// Function prototypes
void UserOnCreateDevice();
void UserOnEndScene();
DWORD UserOnWndProc();
//------------------------------------------------------------------------
// Higher level functions called via AntTweakbar buttons
void TW_CALL RunSnow(void * clientData)
{
printf("RunSnow called!\n");
}
void TW_CALL RunSun(void * clientData)
{
printf("RunSun called!\n");
}
void TW_CALL RunRain(void * clientData)
{
printf("RunRain called!\n");
}
//------------------------------------------------------------------------
namespace CC_CreateDevice
{
DWORD codecave_CreateDevice_ReturnAddress = 0;
DWORD codecave_CreateDevice_esp = 0;
void StealCreateDeviceParameters()
{
// 7 params, 4 bytes each = 28 total bytes to copy
memcpy(&SilkroadD3D, UlongToPtr(codecave_CreateDevice_esp), 28);
}
void ProcessCreateDeviceParameters()
{
// We want to keep higher level code separate for this section of low level code
UserOnCreateDevice();
}
/*
00A62FE8 |. FFD0 CALL NEAR EAX ; d3d->CreateDevice
00A62FEA |. 8B4D 00 MOV ECX,DWORD PTR SS:[EBP]
*/
__declspec(naked) void codecave_CreateDevice()
{
// Save where we just came from
__asm pop codecave_CreateDevice_ReturnAddress
// Store the top of the stack so we can steal all the parameters in one go
__asm mov codecave_CreateDevice_esp, esp
// Save registers before we call our function
__asm pushad
// User function
StealCreateDeviceParameters();
// Restore registers
__asm popad
// Original client code
__asm
{
CALL NEAR EAX
MOV ECX,DWORD PTR SS:[EBP]
}
// Save registers before we call our function
__asm pushad
ProcessCreateDeviceParameters();
// Restore registers
__asm popad
// We need to return from where we came from now
__asm push codecave_CreateDevice_ReturnAddress
__asm ret
}
}
//------------------------------------------------------------------------
namespace CC_EndScene
{
DWORD codecave_EndScene_ReturnAddress = 0;
void OnEndScene()
{
// We want to keep higher level code separate for this section of low level code
UserOnEndScene();
}
/*
00A5F05E . 50 PUSH EAX
00A5F05F . FFD2 CALL NEAR EDX ; d3d->EndScene
00A5F061 . B0 01 MOV AL,1
*/
__declspec(naked) void codecave_EndScene()
{
// Save where we just came from
__asm pop codecave_EndScene_ReturnAddress
// Save registers before we call our function
__asm pushad
// User function to call before the EndScene function is called
OnEndScene();
// Restore registers
__asm popad
// Original client code
__asm
{
PUSH EAX
CALL NEAR EDX
MOV AL, 0x01
}
// We need to return from where we came from now
__asm push codecave_EndScene_ReturnAddress
__asm ret
}
}
//------------------------------------------------------------------------
namespace CC_WndProc
{
DWORD codecave_WndProc_ReturnAddress = 0;
DWORD codecave_WndProc_esp = 0;
DWORD codecave_WndProc_result = 0;
void OnWndProc()
{
// 4 params, 4 bytes each = 16 total bytes to copy
memcpy(&SilkroadWndProc, ((LPBYTE)ULongToPtr(codecave_WndProc_esp)) + 4, 16);
// We want to keep higher level code separate for this section of low level code
codecave_WndProc_result = UserOnWndProc();
}
/*
007324F0 . 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
--> 007324F4 . 3D 96040000 CMP EAX,496
--> 007324F9 . 75 30 JNZ SHORT sro_clie.0073252B
007324FB . A1 38CAF500 MOV EAX,DWORD PTR DS:[F5CA38]
00732500 . 56 PUSH ESI
00732501 . 8B70 24 MOV ESI,DWORD PTR DS:[EAX+24]
00732504 . 68 ACC7D300 PUSH sro_clie.00D3C7AC
00732509 . 8BCE MOV ECX,ESI
0073250B . E8 B0023100 CALL sro_clie.00A427C0
00732510 . 84C0 TEST AL,AL
00732512 . 74 11 JE SHORT sro_clie.00732525
00732514 . 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+14]
00732518 . 8B5424 10 MOV EDX,DWORD PTR SS:[ESP+10]
0073251C . 51 PUSH ECX
0073251D . 52 PUSH EDX
0073251E . 8BCE MOV ECX,ESI
00732520 . E8 6B660300 CALL sro_clie.00768B90
--> 00732525 > 33C0 XOR EAX,EAX
00732527 . 5E POP ESI
--> 00732528 . C2 1000 RETN 10
0073252B > 8B0D 3CCAF500 MOV ECX,DWORD PTR DS:[F5CA3C]
00732531 . 894424 08 MOV DWORD PTR SS:[ESP+8],EAX
00732535 . E9 E6853200 JMP sro_clie.00A5AB20
*/
__declspec(naked) void codecave_WndProc()
{
// Save where we just came from
__asm pop codecave_WndProc_ReturnAddress
__asm mov codecave_WndProc_esp, esp
// Save registers before we call our function
__asm pushad
// User function to call before the EndScene function is called
OnWndProc();
__asm cmp codecave_WndProc_result, 0
__asm jne LABEL_1
// Restore registers
__asm popad
// Original client code (we do not pop esi since we never pushed it in the first place!)
__asm xor EAX, EAX
__asm ret 0x10
LABEL_1:
// Restore registers
__asm popad
// Original client code
__asm
{
CMP EAX, 0x496
}
// We need to return from where we came from now
__asm push codecave_WndProc_ReturnAddress
__asm ret
}
}
//------------------------------------------------------------------------
// The function where we place all our logic
void UserOnInject()
{
edx::CreateConsole("SilkroadFramework");
// Mutex for the launcher, no patches required to start Silkroad now
CreateMutexA(0, 0, "Silkroad Online Launcher");
CreateMutexA(0, 0, "Ready");
edx::CreateCodeCave(0xA62FE8, 5, CC_CreateDevice::codecave_CreateDevice);
edx::CreateCodeCave(0xA5F05E, 5, CC_EndScene::codecave_EndScene);
edx::CreateCodeCave(0x7324F4, 5, CC_WndProc::codecave_WndProc);
}
//------------------------------------------------------------------------
// User function called right after CreateDevice
void UserOnCreateDevice()
{
if(bIsAntInitialized == true)
{
TwTerminate();
bIsAntInitialized = false;
}
// Setup the Tweakbar
if(!TwInit(TW_DIRECT3D9, *SilkroadD3D.device))
{
MessageBoxA(0, TwGetLastError(), "Cannot initialize AntTweakBar", MB_OK | MB_ICONERROR);
}
else
{
// Mark this for our own reference
bIsAntInitialized = true;
// Create the first bar
mainBar1 = TwNewBar("edxLabs :: SilkroadFramework :: Bar 1");
TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar in a DirectX9 application.' "); // Message added to the help bar.
TwDefine(" TweakBar color='128 224 160' text=dark "); // Change TweakBar color and use dark text
TwAddVarRW(mainBar1, "NumSec", TW_TYPE_INT32, &numSec, " label='Strip length' min=1 max=1000 keyIncr=s keyDecr=S help='Number of segments of the strip.' ");
TwAddVarRW(mainBar1, "Color", TW_TYPE_COLOR3F, &color, " label='Strip color' ");
TwAddVarRW(mainBar1, "BgColor", TW_TYPE_COLOR32, &bgColor, " label='Background color' ");
TwAddVarRO(mainBar1, "Width", TW_TYPE_INT32, &SilkroadD3D.presentationParameters->BackBufferWidth, " label='wnd width' help='Current graphics window width.' ");
TwAddVarRO(mainBar1, "Height", TW_TYPE_INT32, &SilkroadD3D.presentationParameters->BackBufferHeight, " label='wnd height' help='Current graphics window height.' ");
// Create the second bar
mainBar2 = TwNewBar("edxLabs :: SilkroadFramework :: Bar 2");
TwAddButton(mainBar2, "buttonSnow", RunSnow, NULL, " label='Snow' ");
TwAddButton(mainBar2, "buttonSun", RunSun, NULL, " label='Sun' ");
TwAddButton(mainBar2, "buttonRain", RunRain, NULL, " label='Rain' ");
}
}
// User function called right before EndScene
void UserOnEndScene()
{
// Always be sure to check for these things
if(bIsAntInitialized)
{
TwDraw();
}
}
// User function called on the WndProc
DWORD UserOnWndProc()
{
// Do not let the TweakBar use the cursor since it conflicts with Silkroad's
if(SilkroadWndProc.message == WM_SETCURSOR)
{
// Return 0 to not pass the event to the client either.
return 0;
}
// Check to see if the event should be handled by the TweakBar
if(TwEventWin(SilkroadWndProc.hWnd, SilkroadWndProc.message, SilkroadWndProc.wParam, SilkroadWndProc.lParam))
{
return 0;
}
// The message was not handled and should be sent to the client
return 1;
}
//------------------------------------------------------------------------
That was quite a guide if I might say! There is a lot of knowledge to digest and understand, but if you are able to make it through this guide, then you are well on your way to being able to do a lot more cool stuff with the Silkroad client. As I mentioned in the beginning, this guide is still a foundation stepping stone that goes through the hard work we must do first. In future guides, we will actually be making real programs using this knowledge.
That wraps up this guide. After reading all three guides written so far, you already have a large exposure of the work required to reverse the client and add in your own functionality. There are not many new concepts left to cover. Once you learn the basics of being able to create a loader and inject a DLL, find relevant locations in the client, make patches and codecaves, you have all the tools you need to start making advanced programs.
I certainly hope this article was a lot more exciting than the previous. Now that all the boring stuff is out of the way, more advanced and existing articles can be written now. Stay tuned for future guides!
Drew “pushedx” Benton
edxLabs






