PDA

View Full Version : Hack efficiency: Threading



SC_Modder
09-14-2008, 11:22 AM
I've noticed a surprising trend lately of "if it works then it's fine" in regards to hack development. There are usually more efficient paths that can be taken if you apply some thinking.

This particular tutorial will focus on threading.

You may, in a hack, have felt the need to check for a condition every milisecond, or perform an action every second. While in most normal programming cases, you would create a thread (http://msdn.microsoft.com/en-us/library/ms682453.aspx) for this sort of thing, along with using Sleep() (http://msdn.microsoft.com/en-us/library/ms686298(VS.85).aspx) to create the delay. However, when you add more and more "features" to your hack, I've noticed several people (coughagentcough) tend to create a separate thread for every single timer. This is just plain inefficient and wasteful of CPU cycles.

Consider the following piece of code:


DWORD WINAPI MyConditionThread(LPVOID lpParam)
{
for(;;) // loop indefinitely
{
PerformCondition();
Sleep(1); // sleeping for 1 is approximately ~10ms depending on conditions
// it is mostly used to prevent 100% CPU usage
}
}

DWORD WINAPI MyActionThread(LPVOID lpParam)
{
for(;;) // loop indefinitely
{
PerformAction();
Sleep(1000); // wait 1 second and do it again
}
}


Hopefully if you are an experienced programmer you know where I am going with this by now.

While this obviously works, you are adding two more threads to the queue. Starcraft itself with all of the processing it has to do only has five threads, and only one of which is in charge of handling all game changes and drawing the screen. If starcraft can do all that in a single thread, we should be more than able to ourselves.

There is a better way. If we combine both threads together and use GetTickCount() (http://msdn.microsoft.com/en-us/library/ms724408.aspx) for timing, we can only use one thread to do the action and check for the condition.



DWORD WINAPI MyCombinedThread(LPVOID lpParam)
{
DWORD dwPreviousActionTick = 0; // the tick when we last called PerformAction()
for(;;) // loop indefinitely
{
PerformCondition();
if( GetTickCount() - dwPreviousActionTick >= 1000 ) // has one second elapsed?
{
PerformAction();
dwPreviousActionTick = GetTickCount(); // reset counter
}
Sleep(1); // prevent 100% cpu usage by this thread
}
}

Doing it this way is clearly more effective. You can add lots of function checks into a single thread this way. However there is an even better way to do it. Remember when I mentioned starcraft has a drawing thread that handles all of its screen drawing and game changes? Why don't we hook into this thread and perform our checks from there? While it does require writing a hook to starcraft's memory, doing it this way is more effective because not only do we not add an unnecessary thread to the system, checking for a condition more often than every frame is redundant; the majority of starcraft's data only changes on a per-frame basis. Also, if you have a hook in your hack for drawing to starcraft's screen already then you don't have to add anything (I will not cover creating hooks in this tutorial). You can simply add your conditions to it like so:


void MyDrawingRoutine() // the function that draws everything, called from SC hook
{
static DWORD dwPreviousActionTick = 0; // the tick when we last called PerformAction()

// static keyword denotes that the variable will not be re-initialized when function
// is called again, meaning it will keep its value

DrawGarbage(); // perform drawing stuff

PerformCondition();
if( GetTickCount() - dwPreviousActionTick >= 1000 ) // has one second elapsed?
{
PerformAction();
dwPreviousActionTick = GetTickCount(); // reset counter
}
}

Notice the lack of a for() loop. This is because the for() loop is already in starcraft's drawing thread and this function is called once per frame. Adding a for loop would hang the drawing thread indefinitely.

Also note the lack of a Sleep() call: While it will actually prevent starcraft from using 100% on single-core systems, on older systems this will cause lower framerates because once Sleep is called, the CPU will start processing other threads in the system before continuing execution in this thread. (This was actually the basis of my Sleepy hack, except mine would only call Sleep() if it calculated the frame rate to be at least 35fps.)

Hopefully you nooby hack makers have learned a few things about threading today. I'm planning on writing more tutorials about other programming/hacking concepts.

dt
09-14-2008, 11:32 AM
Good job, that's some pretty important points for n00b programmers to learn.