Register for your free account! | Forgot your password?

Go Back   elitepvpers > Popular Games > Silkroad Online > SRO Coding Corner
You last visited: Today at 20:00

  • Please register to post and access all features, it's quick, easy and FREE!

Advertisement



[Guide] Integrating AntTweakBar into Silkroad

Discussion on [Guide] Integrating AntTweakBar into Silkroad within the SRO Coding Corner forum part of the Silkroad Online category.

Reply
 
Old   #1

 
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,780
[Guide] Integrating AntTweakBar into Silkroad

This next article in my series is a fun one! We will be integrating the AntTweakBar library into the client using the knowledge gained from our two previous articles. This article is pretty knowledge heavy as well as readers will be full exposed to assembly, codecaves, and general programming. However, by going through this guide as well as the previous two, readers should be able to build up a good knowledge base of learning how to develop for the Silkroad client.

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:

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
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!

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;
};
Just on a side note, I’ve been working with this stuff since late 2006, which is when I got started. In all that time, from 2006 until yesterday as I was writing this guide I have never thought about the method I am about to show to get the parameters of the function quick and easily. Just think, I’ve been doing this for a little over 2 years and just now learned a better way to do things! That should just goes to show you how deep this domain really is, you really can learn new things every day!

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};
Now comes the tricky part to explain. We first have to locate the WndProc in the client. You can use the methods previously explained. If you had trouble finding it or just want to be sure, here is the entry point in the current version of Silkroad at time of this writing (1.199):


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
	}
}
The way a WndProc works is by checking the message being passed and determining if the message should be handled or not. In our case, we use an external variable, codecave_WndProc_result, to hold that decision. We invoke a user function that has not been shown yet to perform the higher level logic because we want to keep the low level code separate from the higher level code to make maintenance easier.

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;
};
Just like before, we also want to create a global object as we cannot create a local object:

Code:
// Object that holds our clients Direct3D information
tSilkroadD3D SilkroadD3D = {0};
Finally we have the function for our codecave logic, which will work in a similar manner as the WndProc codecave:

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
	}
}
By using the methods shown in the previous Direct3D guide, you should be able to see that the CreateDevice function is called in the client at the following location:


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
	}
}
Phew! That is the bulk of our work for this guide. Have a good look over it, even if you do not understand every little detail yet, it’s ok. With these functions complete, we can now implement the codecave writing process via our UserOnInject function. Creating the codecaves is pretty simple. We just need the address to write the codecave at, the size of the patch, and the function to call. We always need at least 5 bytes, but we can use as many more as needed as long as we properly handle the overwritten code in our codecave.

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 you might notice, we have to hard code the addresses. This means client updates might break the code, but that is ok because we can just update it again. If you had some time, you could look into a solution to auto update the code for you, but that is beyond the scope of this simple guide.

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");
}
Next, we have our custom higher level user functions our lower level code calls:

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;
}
Now, we just need to link in the AntTweakBar to the project and include the relevant header files for the library and Direct3D. This is how the top of the file should look:

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")
Now, you should be able to compile (well after using the upcoming code listing) and run the loader to inject the DLL into the client. Make sure you have copied the AntTweakBar.dll file into the Silkroad directory! If all goes well, you should be able to see the new GUI integrated into the client. You should also notice the cursor is now a Windows cursor rather than the client cursor. This is just a side effect I had to get working since AntTweakBar interferes with the regular Silkroad cursor. I’m not sure how to fix this yet.

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;
}

//------------------------------------------------------------------------
V. Conclusion

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
pushedx is offline  
Thanks
30 Users
Old 06/24/2009, 16:55   #2
 
elite*gold: 0
Join Date: Jun 2008
Posts: 188
Received Thanks: 106
Awesome guide. Can't wait for next ones !
maxbot is offline  
Old 06/27/2009, 12:33   #3
 
strukel's Avatar
 
elite*gold: 20
Join Date: Jul 2007
Posts: 2,215
Received Thanks: 1,360
#stick
strukel is offline  
Reply


Similar Threads Similar Threads
[Guide] Silkroad Development Series
10/08/2025 - SRO Coding Corner - 23 Replies
In order to help organize the guides I release, I am making this additional thread to point to all of the individual guides so everything can be accessed from one convenient location. As more guides are released I will edit this thread to link to them. Please leave guide specific comments in the respective guide. Thank you and enjoy. :) Silkroad Development Series: 1. Locating Silkroad’s Direct3D Objects 2. Creating a Simple Loader with Injected DLL for Silkroad 3. Integrating...
[Guide] silkroad que system
09/12/2009 - SRO Hacks, Bots, Cheats & Exploits - 8 Replies
Hey you guys.. i know silkroad que system is making all of us mad... How we cant connect even if servers populated. i know theres a sticky for how to solve it. but this is my way of solving it.. Use a loader.. Testosterone loader automatically connects you to the shortest gate way. I dont know how they did it but i usually connect to xian in 40 mins with the loader sometimes 15 mins.. link is Recyfer Inc. when you extract it pass is recyfer.info. extract it to your silkroad folder...
[GUIDE] Installing Silkroad on Linux
09/10/2008 - SRO Guides & Templates - 4 Replies
Over teh few months people been emailing and PMing me at asking if i ever post a guide on how to run Silkroad on linux First of all u dont need any skills about coding and etc... just one skill u need its called: "Internet Savy" System requirements: Operating System: Ubuntu Linux (Hardy heron 8.04 LTS) RAM: 512 mb
Silkroad Online Guide From A to Z!
04/14/2008 - SRO Guides & Templates - 27 Replies
Hello elite forum. i wanted to post a guide for the whole game and i think this is will be usefull for some of u as long as there is still some beginers in this game . Registring and downloading the game. To registed go to JOYMAX.COM - Global Hub of Fun :: JOYMAX choose create account and add ur user name u want and the password and ur e-mail



All times are GMT +1. The time now is 20:00.


Powered by vBulletin®
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
SEO by vBSEO ©2011, Crawlability, Inc.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Support | Contact Us | FAQ | Advertising | Privacy Policy | Terms of Service | Abuse
Copyright ©2026 elitepvpers All Rights Reserved.