Tutorials for Experienced
You are the regular coder but want to reach higher levels of engine coding, special hacking techniques, then it's in here. We surely can meet your level and exceed it, if not ? Then post a tutorial yourself ! Get known !!!!

Go Back   The World of Game Hacking > GameHacking Articles > Tutorials for Experienced

IRC Rules
Post New Thread  Reply
 
LinkBack Thread Tools Display Modes
  (#1 (permalink)) Old
Supervisor
 
attilathedud's Avatar
 


64-Bit Member

 
Posts: 242
Join Date: Sep 2006
Location: in ur program, making massive overheads
Last Online: Today 04:26 PM
Reputation: attilathedud is on a distinguished road
User is Offline
usa
   
Adding basic DIB effects to a trainer - 07-07-2009, 08:07 PM

Depression is quite the cue for writing. Oh well - don't feel bad, lines of code do an amazing job of dulling affect, and even more, we get to cover DIB effects! I remembered when I started getting into writing trainers, the only resources available covering DIB's were [sheep]'s and micral's tutorials; don't get me wrong, both of these tutorials are great, but for people just starting coding trainers (or coding in general), the explanations offered in both are very terse.

*Do not misinterpret me, if you are new to coding, I would suggest you stray away from launching into DIB's.*

*This tutorial builds upon the code we established in the last tutorial: Separating the injector and the hack - DoxCoding Forums . The only thing we are going to be modifying is the GUI.*

Before we begin working on any DIB effects, we need to edit our trainer slightly - first we need to alter the window style, so the whole thing flows better together; we also need to adjust the size so we can fit all of our effects. So edit our CreateWindow call to look like:
Code:
   hwnd = CreateWindow(wc.lpszClassName, "TrainerEngine", WS_POPUP,  (GetSystemMetrics(SM_CXSCREEN)-400)/2, (GetSystemMetrics(SM_CYSCREEN)-300)/2, 400, 300, NULL, NULL, hInstance, NULL);
*The WS_POPUP style declares a window with no border, or no title bar.*

With our window enlarged, we now need to modify the location of our buttons so that they reside on the bottom:
Code:
         CreateWindow("button", "Start Game", WS_VISIBLE | WS_CHILD, 25, 278, 100, 20, hWnd, (HMENU)1, NULL, NULL);
            CreateWindow("button", "About", WS_VISIBLE | WS_CHILD, 150, 278, 100, 20, hWnd, (HMENU)3, NULL, NULL);
            CreateWindow("button", "Exit", WS_VISIBLE | WS_CHILD, 275, 278, 100, 20, hWnd, (HMENU)2, NULL, NULL);
Since our window now lacks a title bar, the user has no way to move it - to solve this, we are going to write a new case for our WndProc to interpret the left mouse key:
Code:
		case WM_LBUTTONDOWN:
			
			break;
Now, whenever the user presses the left mouse key, we want the window to be "grabbed" and allowed to be move - to do this, we are going to use SendMessage to lock the window in a drag-able mode:
Code:
SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam);
With our window now moveable, we also need to give the user a way to exit without using the exit button; for this, we are going to exit our program whenever they hit the escape key:
Code:
		case WM_KEYDOWN:
			if(wParam == VK_ESCAPE)
				PostQuitMessage(0);
			break;
*Whenever WM_KEYDOWN is the case, the wParam parametre ([ebp+10h]) holds the virtual key-code.*

*Most of the y calculations in this tutorial are based off using the background included in the .ZIP at the end of this file. Adjust if you change the dimensions. Also, it is possible to over the DIB plane over the whole window, and load in .RAW images. Both ways work.*

Before we can set up our DIB'ing interface, we need to first set up a BITMAPINFO structure to pass to it - under WinMain, we need to declare it and fill it with values:
Code:
	BITMAPINFO plane = {0};

	plane.bmiHeader.biSize = 40;			//size of the structure, can also use sizeof() macro
	plane.bmiHeader.biWidth = 400;			//width of our plane
	plane.bmiHeader.biHeight = -205;		//height
	plane.bmiHeader.biPlanes = 1;			//one layer
	plane.bmiHeader.biBitCount = 32;		//32 bit colour
*Why the negative? Because, if the height is positive, our identity axis (0, 0) will be at the lower-left hand corner. With a negative value, (0, 0) rests at the upper-left hand corner.*

*Before we can continue, we need to declare two global variables - under your includes, and before your WndProc, add:
Code:
HDC dc = 0;
unsigned char* buffer;
*

First, before we can create our DIB section, we need to grab a valid device context handle; to do this, we are going to get the current device context, and then pass it CreateCompatibleDC to give us an active handle we can draw on. Right after our call to CreateWindow place:
Code:
	dc = CreateCompatibleDC(GetDC(hwnd));
With a nice device context, we can now call CreateDIBSection:
Code:
	SelectObject(dc, CreateDIBSection(dc, &plane, 0, (void**)&buffer, 0, 0));
*SelectObject selects a specific device context for use.*

Buffer will become our drawing plane; it is easiest to think of it as a series of on's (1) and off's (0). For example, a square on a 4x4 plane would look like:
Quote
1111
1001
1001
1111
However, we cannot sum it up like this, because behind each of those one's is a full dword that represents the colour - it takes the form RGBA (Red, Green, Blue, Alpha). If we wanted a red dot for example:
Quote
FF000000
*You can safely ignore the alpha value for now, we won't be using it in this project.*

*Make sure to note that we pass pointers to our plane structure, and our buffer.*

*Some may be confused as to why we declared buffer as a null terminating string (char*) - that's for letters right? Incorrect, the only thing the character type identifies a variable as is that it can hold -127 to 127. To extend its range, we are going to modify it with the "unsigned" keyword that extends its range from 0 to 255(FF). Since that's all we need for an individual colour, we are good to go.*

With our DIB plane set up, we next need to set the background mode to transparent, and set the text colour to white so we can draw text on it:
Code:
	SetBkMode(dc, TRANSPARENT);
	SetTextColor(dc, 0xffffff);
*Just like white is a combinations of all the colours when it comes to light, FFFFFFh represents white in hex.*

*0x designates hex in C++.*

With our buffer now set up, we need to go back to the WndProc to ensure that it is painted on the screen. First, since we are going to be calling BeginPaint, we need a PAINTSTRUCT to pass to it - under the gamePath and path definitions:
Code:
	PAINTSTRUCT ps = {0};
Next we need to declare our WM_PAINT case in our WndProc:
Code:
		case WM_PAINT:
			break;
First we are going to retrieve an open drawing handle to our device, and then call BitBlt - which draws pixels from one buffer (our plane) to another (our window). After that, we are going to call EndPaint to close our handle, and then we will be good to go:
Code:
			BitBlt(BeginPaint(hWnd, &ps), 0, 69, 400, 205, dc, 0, 0, SRCCOPY);
			EndPaint(hWnd, &ps);
*The 69 represents the y coordinate we want to start drawing at, since we want to reserve our background's title.*

We can now write a function to draw an individual pixel - at the top of your program, before the WndProc, but after the globals:
Code:
void placePixel(int x, int y, unsigned char r, unsigned char g, unsigned char b)
{
	register int loc = 0;

	loc = (y * 400) + x;
	buffer[loc*4] = b;
	buffer[loc*4+1] = g;
	buffer[loc*4+2] = r;
}
*The register keyword attempts to reserve a single register for our variable - it is a good idea, since we are essentially moving several values quickly.*

Let's start dissecting this function, because for those of you not working with DIB's, this may appear a bit confusing. Taking in an x and y coordinate, plus the three primary colours, we then determine the pixel's actual location. Actual? That's right, we can't draw to a specific x and y, we instead need to determine where the x and y coordinates we are provided land us. Take, for example, if we pass the value (20, 2). First we start off by factoring in the y aspect; we know that each line has 400 pixels, so if we want to move two lines down, we already need to get to the 800'th pixel range:
Quote
2*400 = 800 + x
Now all we have left to do is shift how many x pixels we established:
Quote
800 + 20 = 820
From this example we can see that the actual location of (20, 2), in our computer's view at least, resides at pixel 820.

With our actual location, we can begin to shift our values into the corresponding array element - we reference the location * 4 (to compensate for the fact each pixel is a dword), and then move the blue hex value into the first byte, green into the second byte (+1), and red in the the third (+2).

*Why are we moving the values in BGR order? Because we need to compensate for Intel's little endianness architecture that reads dwords "backwards."*

With our plane all set up, let's start throwing in some basic effects. We are going to start off with the classic starfield effect - stealing micral's idea, we are going to create a small structure for each of our stars. At the top of the program, before our placePixel function:
Code:
struct star
{
	short int x;
	short int y;
};
*The short keyword reduces the range of your integer to two bytes instead of the normal four.*

*Yes, an integer and a DWORD are essentially the same thing.*

With that declare, right underneath we can declare our arrays of stars - we are going to make it two layers, one moving and one static:
Code:
star starfield[50] = {0, 0};
star background[300] = {0, 0};
*Before we can start, we need to declare a counter variable in the global section:
Code:
int i = 0;
*

Our arrays declared, we now need to initialise them; under our WM_CREATE case:
Code:
			for(i = 0; i < 300; i++)
			{
				if(i < 50)
				{
					starfield[i].x = rand()%400;
					starfield[i].y = rand()%200+1;
				}
				background[i].x = rand()%400;
				background[i].y = rand()%200+1;
			}
This basically loops through every element of each array(cutting off at fifty to avoid overflowing the starfield array) and fills the element with a random value from zero to whatever the number after the modulo holds.

*The +1 for the y is to ensure that none of the stars land on our separator.*

Since we need to move our stars and constantly refresh the screen, the best way to do this is through the use of a timer - under the WM_CREATE case still:
Code:
SetTimer(hWnd, 4, 10, 0);
*This basically sets a timer with an ID of four that sends out a command every ten milliseconds.*

*Before we continue, we need to make sure to clean up the timer when we exit; under the WM_DESTROY case:
Code:
KillTimer(hWnd, 4);
*

Our timer set up, we need to declare the case to handle it:
Code:
case WM_TIMER:

	break;
Since our plane is in constant movement, we need to ensure that we refresh the screen with each call to WM_TIMER, so that our buffer doesn't become corrupted with odd pixels:
Code:
memset(buffer, 0, 328000);
*400(width) * 200(height) * 4(dword) = 328000*

A clear buffer, we can start drawing our stars - let's start first with our static objects, the background stars and the separator bar:
Code:
			for(i = 0; i < 400; i++)
			{
				if(i < 300)
					placePixel(background[i].x, background[i].y, 100, 100, 100);

				placePixel(i, 0, 250, 250, 250);
			}
Next, we are going to set up the loop to handle our moving stars:
Code:
			for(i = 0; i < 50; i++)
			{

			}
First we need to factor in making the stars move - in the for loop:
Code:
				if(starfield[i].x-- < 2)
				{
					starfield[i].y = rand()%200+1;
					starfield[i].x = 395;
				}
Nothing too complex here, all we are doing is decreasing the x coordinate of each star each time the loop is called, and if the x coordinate is less than two, then we move the star to the right side of the screen, randomising the y coordinate with each pass. With our coordinates determined, we can draw our stars:
Code:
				placePixel(starfield[i].x, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-1, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-2, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-3, starfield[i].y, rand()%255, rand()%255, rand()%255);
*The multiple calls ensure that a small line is drawn instead of a single pixel.*

Finally, we need to finish up our WM_TIMER case by refreshing the entire window:
Code:
			RedrawWindow(hWnd, 0, 0, RDW_INVALIDATE);
Compile and link, and you should see a very nice starfield! Time to finish this trainer up with some nice text overlaid on the starfield - first, in our WndProc, we need to declare a rectangle that will hold the initial x and y for our text to be drawn to:
Code:
	RECT rect = {100, 50, 70, 70};
With our rectangle set up, we are now going to call DrawText; before the call to RedrawWindow:
Code:
			DrawText(dc, "= Windows XP MineSweeper + 1 =\n   Code..................attilathedud\n   Graphics............shashank\n   Tune......................", 130, &rect, DT_NOCLIP);
*130 represents the character count, and DT_NOCLIP alerts the DrawText function to forget formatting when it comes to the actual length of the rectangle. You can also pass -1 to DrawText if the string passed is a pointer to a null-terminating string. Up to you.*

Build it again, and you will notice lovely text!

The final code:
Code:
#include <windows.h>
#include "inject.h"
#include "resource.h"

#define WIN32_LEAN_AND_MEAN

HDC dc = 0;
int i = 0;
unsigned char* buffer;

struct star
{
	short int x;
	short int y;
};

star starfield[50] = {0, 0};
star background[300] = {0, 0};

void placePixel(int x, int y, unsigned char r, unsigned char g, unsigned char b)
{
	register int loc = 0;

	loc = (y * 400) + x;
	buffer[loc*4] = b;
	buffer[loc*4+1] = g;
	buffer[loc*4+2] = r;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    OPENFILENAME ofn;
    char gamePath[256] = {0}, path[256] = {0};
	PAINTSTRUCT ps = {0};
	RECT rect = {100, 50, 70, 70};

    switch(msg)
    {
        case WM_CREATE:
            CreateWindow("button", "Start Game", WS_VISIBLE | WS_CHILD, 25, 278, 100, 20, hWnd, (HMENU)1, NULL, NULL);
            CreateWindow("button", "About", WS_VISIBLE | WS_CHILD, 150, 278, 100, 20, hWnd, (HMENU)3, NULL, NULL);
            CreateWindow("button", "Exit", WS_VISIBLE | WS_CHILD, 275, 278, 100, 20, hWnd, (HMENU)2, NULL, NULL);
			SetTimer(hWnd, 4, 10, 0);
			for(i = 0; i < 300; i++)
			{
				if(i < 50)
				{
					starfield[i].x = rand()%400;
					starfield[i].y = rand()%200+1;
				}
				background[i].x = rand()%400;
				background[i].y = rand()%200+1;
			}	
            break;
		case WM_PAINT:
			BitBlt(BeginPaint(hWnd, &ps), 0, 69, 400, 205, dc, 0, 0, SRCCOPY);
			EndPaint(hWnd, &ps);
			break;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case 1:
                    ZeroMemory(&ofn, sizeof(ofn));
                    ofn.lStructSize = sizeof(ofn);
                    ofn.lpstrFile = gamePath;
                    ofn.lpstrFile[0] = '\0';
                    ofn.nMaxFile = sizeof(gamePath);
                    ofn.lpstrFilter = ".Exe\0*.EXE\0";
                    ofn.lpstrTitle = "Please Locate Game";
                    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
                    
                    if(GetOpenFileName(&ofn))
                    {
                        GetModuleFileName(NULL, path, sizeof(path));
                        for(i = strlen(path); path[i] != '\\'; i--)
                            path[i] = 0;
                        strcat(path, "dibInjectorDll.dll");
                    
                        PROCESS_INFORMATION pI = startProcess(gamePath);
                        inject(path, pI);

                        ResumeThread(pI.hThread);
                        CloseHandle(pI.hThread);
                        PostQuitMessage(0);
                    }
                    break;
                case 2:
                    PostQuitMessage(0);
                    break;
                case 3:
                    MessageBox(hWnd, "Trainer for Windows XP MineSweeper.\n Hit F7 in-game to set timer to 0. \n Coded by attilathedud.\n Graphics by shashank & attilathedud.\n DoxCoding.com.", "TrainerEngine", MB_OK);
            }
            break;
		case WM_TIMER:
			memset(buffer, 0, 328000);
			
			for(i = 0; i < 400; i++)
			{
				if(i < 300)
					placePixel(background[i].x, background[i].y, 100, 100, 100);

				placePixel(i, 0, 250, 250, 250);
			}

			for(i = 0; i < 50; i++)
			{
				if(starfield[i].x-- < 2)
				{
					starfield[i].y = rand()%200+1;
					starfield[i].x = 395;
				}
				placePixel(starfield[i].x, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-1, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-2, starfield[i].y, rand()%255, rand()%255, rand()%255);
				placePixel(starfield[i].x-3, starfield[i].y, rand()%255, rand()%255, rand()%255);
			}
			DrawText(dc, "= Windows XP MineSweeper + 1 =\n   Code..................attilathedud\n   Graphics............shashank\n   Tune......................", 130, &rect, DT_NOCLIP);
			
			RedrawWindow(hWnd, 0, 0, RDW_INVALIDATE);
			break;
		case WM_LBUTTONDOWN:
			SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam);
			break;
		case WM_KEYDOWN:
			if(wParam == VK_ESCAPE)
				PostQuitMessage(0);
			break;
        case WM_DESTROY:
			KillTimer(hWnd, 4);
            PostQuitMessage(0);
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    HWND hwnd;
    WNDCLASS wc = {0};
    MSG msg = {0};
	BITMAPINFO plane = {0};

	plane.bmiHeader.biSize = 40;
	plane.bmiHeader.biWidth = 400;
	plane.bmiHeader.biHeight = -205;
	plane.bmiHeader.biPlanes = 1;
	plane.bmiHeader.biBitCount = 32;

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ID_ICON));
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = "TrainerEngine";
    wc.hbrBackground = CreatePatternBrush(LoadBitmap(hInstance, (char*)ID_BACK));

    RegisterClass(&wc);
    
    hwnd = CreateWindow(wc.lpszClassName, "TrainerEngine", WS_POPUP,  (GetSystemMetrics(SM_CXSCREEN)-400)/2, (GetSystemMetrics(SM_CYSCREEN)-300)/2, 400, 300, NULL, NULL, hInstance, NULL);

	dc = CreateCompatibleDC(GetDC(hwnd));
	SelectObject(dc, CreateDIBSection(dc, &plane, 0, (void**)&buffer, 0, 0)); 
	SetBkMode(dc, TRANSPARENT);
	SetTextColor(dc, 0xffffff);

	ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    UnregisterClass("TrainerEngine", hInstance);

    while(1)
    {
        GetMessage(&msg, NULL, 0, 0);

        if(msg.message == WM_QUIT)
            break;
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return 0;
}
Now obviously, we have left out a very important aspect of any trainer - the tunes. However, with a lot of documentation on using both uFMOD and miniFMod, I'm sure you can figure it out on your own. Remember, hacking is all about experimentation, so I wish you the best of luck! Until next time,

<3 attilathedud

Shoutouts:

STN - For his DVT trainer-template which gave me a lot of ideas, and for helping me with debugging. He be sexy. And he's still the star-field god.

atam0s - For helping debugging and throwing out suggestions for how to append a timer to the project. Wouldn't have been possible without you mate, thanks.

King_Orgy - For testing, being a sexy dude.

pandas - He's always going to have a shoutout for helping me when I was new. Thanks oldie.

Written for doxcoding.com, make sure credits stay intact if posted elsewhere.

Last edited by attilathedud; 07-08-2009 at 04:22 AM..
  
Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes




New To Site? Need Help?


All times are GMT +1. The time now is 07:35 PM.


Powered by vBulletin
Copyright ©1995 - 2009 GameHacking.com & CES