How I Built a Working Online Poker Bot, Part 7: Extracting Text from 3rd-Party Applications

Thursday, July 17, 2008


Lately I've been doing some programmatic graffiti.

Maybe I have too much time on my hands, but sooner or later every programmer wonders:

  • How to extract text displayed by other applications.
  • How to draw your own text in the windows of external applications.

Maybe you've even wondered:

  • How to hide the text displayed by other applications.

That's fun, too. Minimalism.

I like it.

But why stop with a single application? Let's replace every piece of text drawn anywhere on the system with our tag, creating a harmless "graffiti bomb". Scroll down if you're curious.

Why Are You Vandalizing Your Computer Screen?

To illustrate a technique.

In How I Built a Working Online Poker Bot, Part 4: The Poker Botting Erector Set, we looked at some code to extract text from the Poker Time online poker client software. That was relatively easy because Poker Time windows are, for the most part, standard Windows controls. That means the text they contain is wide-open, to our bot and a hundred other tools, such as Spy++.

Poker Stars and Full Tilt are a little different. Poker Stars and Full Tilt are special. Pull up Spy++ or a similar tool, point it at the Poker Stars game chat window. What do you see? 

Empty text, that's what. Because the Poker Stars game chat window isn't a standard Windows control, with well-defined behavior. It's a custom window, likely implemented using MFC (we know this because of the characteristic "Afx:" window class). Custom windows are allowed to do whatever they want, within reason. Such as return an empty string in response to the WM_GETTEXT message.

"I don't contain any text! Scout's honor!"

Are we fooled?

Today I'm going to show you one way to extract this text. There are others. We'll leave those for future posts, or for your own investigations.

The Text Output Bottleneck

Almost every poker client features a game chat window which contains game actions, chat text, and dealer spam: 

But sometimes getting the text out of this window is a little more complicated than just sending the window a WM_GETTEXT. The method we're going to discuss today is very similar to the method we use to snoop on 3rd-party file I/O. Only this time, instead of detouring the CreateFile/WriteFile/ReadFile functions, we're going to detour the various text-drawing APIs provided by Windows. DrawText and ExtTextOut in particular.

System-Wide Graffiti?

We're going to take advantage of the fact that, by and large, every piece of text drawn by the Windows operating system is routed through a small handful of public APIs. No, I mean virtually EVERY piece of text drawn by your Windows PC is routed through a small handful of functions that display text.

By detouring those functions and replacing them with our own versions (a matter of a few lines of code) we can do things like create a system-wide graffiti bomb. Below, I've replaced every piece of text drawn by every application on the system with a single phrase:

Coding the Wheel was here

This required tens, not hundreds, of lines of code.


Okay. I'm glad we got that straight.

Situated GDI Hooking

If you've been following the botting series, you already know about detouring text-output functions. But if you haven't, understand that whenever an application calls a system API like DrawText or ExtTextOut in order to paint text on the screen, it's going to be calling our code instead. Our code then does four things:

  1. Calls the original version of the function provided by the Windows operating system.
  2. Examines the HDC provided with the call, and gets the associated window as an HWND value. This tells us which window the text is being drawn to.
  3. Examines the location of the text, as provided by point or rectangle coordinates. This tells us what part of the window the text is being drawn to.
  4. Examines the text itself, and does something with it.

I like to call this "situated GDI hooking" because it's installs detours around text-drawing APIs provided by the Graphic Device Interface (GDI) and uses the device context (HDC) along with the X,Y location of the text to establish the meaning of the text: is this game chat text? Miscellaneous window text? Button text? And so forth. 

Injecting the DLL

First, inject a DLL into the target application's process using any DLL injection mechanism.

This has been covered on the Internet, in books, and here on Coding the Wheel, endlessly, so I won't rehash it.

Just get your DLL into the target application's process by any means necessary.

Detouring the Text Output Functions

If you've used the Microsoft Detours library before, you already know the score on this one. For each function you want to detour, you'll need to create an equivalent version taking the same parameters and return type and place it in a DLL. Let's take a look at some sample code for a simple DrawText detour for Poker Stars:

// Function pointer to the original (un-detoured) DrawText API
int (WINAPI * Real_DrawText)(HDC a0, LPCWSTR a1, int a2, LPRECT a3, UINT a4) = DrawTextW;

// Our custom version of DrawText
int WINAPI Mine_DrawText(HDC hdc, LPCWSTR text,  int nCount, LPRECT lpRect, UINT uOptions)
    int rv = Real_DrawText(hdc, text, nCount, lpRect, uOptions);
    HWND hWindow = WindowFromDC(hdc);
    if (!hWindow)
       return rv;

    if (IsPokerStarsChatWindow(hWindow))
       // Text has been written to the Poker Stars chat window.
       HWND hPokerTable = ::GetParent(hWindow);
       // hPokerTable now stores the HWND of the poker table that
       // the chat window belongs to.

    return rv;

And of course, we need to actually install the Detour. The typical time to do this is during the DLL_PROCESS_ATTACH notification.

// Install the DrawText detour whenever this DLL is loaded into any process...
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved  )
    switch (ul_reason_for_call)
        DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText); // <- magic

        DetourDetach(&(PVOID&)Real_DrawText, Mine_DrawText);

    return TRUE;

The other crucial text-drawing API you'll probably want to detour is ExtTextOut (not shown).

How do I tell which window a particular DrawText or TextOut call is drawing to?

As I've said, the text APIs we're detouring get called all over the place. In the case of online poker clients, we're only interested in the text that gets painted in the game chat window. We'd like to ignore all the calls which draw text to other areas of the window.

Here's the basic procedure:

  1. Every call to DrawText, TextOut, and similar APIs includes a device context (HDC) parameter.
  2. Figure out the window associated with this HDC by calling the WindowFromDC function, which returns a window handle (HWND).
  3. Take that window handle and test it to see if it "equals" the window we're interested in. Typically, look at the window's class name and, if necessary, its parent window.

In order to figure out what the window class name is for a particular window, use a tool like Spy++. Here we see that the Poker Stars chat window has a class of "Afx:400000". We also notice that it's a child window of the poker table window. 

Putting all that together, we might have some code that looks like this. The following function returns true if the specified window is the Poker Stars chat window.

bool IsPokerStarsChatWindow(HWND hWindow)
    // The Poker Stars chat window is a child of the poker table window, so
    // if the window doesn't have a parent, it's not the chat window.
    HWND hParent = ::GetParent(hWindow);
    if (hParent)
        // Okay, the window has a parent. Now get the (child) window's class name.
        TCHAR className[255];
        GetClassName(hWindow, className, 255);

        // Compare the window's class name with the PS chat window class name.
        // We know from Spy++ that the Poker Stars chat window has a class of
        // 'Afx:400000:20'. _tcsicmp returns 0 if they're equal.
        return 0 == _tcsicmp(className, _TEXT("Afx:400000:20") );
    return false;

Ultimately, we'd prefer not to hard-code window class names or the parent/child relationships. This information can be passed in from an XML or other settings file, allowing for generic, cross-venue code.

Filtering Out Redundant ExtTextOut and DrawText Calls

As soon as you start to hook the text output APIs you'll notice one thing: TextOut and ExtTextOut get called a lot. Redundantly. Sometimes with empty strings.

In the comments to my last post, a poster by the name of Robert noted:

I have a question for anyone reading... How are you handling the case in which a message is duplicated? Here is an example from a hooked ExtTextOutW to illustrate...

Hooked Function - Table Name = Message
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The flop is [Ts 7s 8h]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: SwimmingPool raises to $10
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: gambleallday folds
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: MGAMW calls $5
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: The flop is [Js 5d 6s]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $10
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $10
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $10

Notice how the turn is "dealt" several times? Also how several bets are duplicated. How can we handle this sort of thing?

Another reader, nickname The Lone Ranger, replied:

Looks like you will need to write a filtering routine. The extent of the filtering will be determined by the frequency and extent of the duplicates. Do the duplicates occur on every hand? Is it always the turn?

I happened to notice both comments shortly after they were posted, so I chimed in.

Yes and one quick and dirty way is to filter by Y value. Provided you don't resize the window or touch the scroll bar, new text tends to enters the window at a constant Y because of the glue effect. Or at least, for any give X,Y associated with a TextOut call, you can answer the question "is this text at the bottom of the window?" The Y may or may not be constant, but it will always be higher than the Y of duped, pushed-upward text.

That's a quick solution to the problem, which is one of DrawText/ExtTextOut spoofing. As new text enters the window, the poker client calls DrawText, ExtTextOut, etc., to draw the text. But it draws that text not only when it's first added to the window, but successively, as new text enters the bottom of the window, forcing the lines above it upward. And depending on exactly how the paint procedure is implemented, the poker client might redraw text at other, seemingly random times.

Another solution would be to get at the actual text stored by the poker client—an approach we'll look at later.

For now, just realize that you'll have to write a little bit of code to answer the question: is the text passed in this particular call to DrawText or TextOut redundant? Here are some ways to do that:

  • First of all, on Poker Stars, don't worry about it. We can bypass redundant messages thanks to a quirk in the drawing logic. This technique was demonstrated in the source code accompanying How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt.
  • On other venues, you can filter (as noted above) by the Y-value passed to the DrawText or ExtTextOut call.
  • You can also store a queue containing the most recent X unique text string you've received via a particular drawing API. When a new call is made, you can check the text against the text you have stored in the list. If it's a dupe, ignore it. Otherwise add it to the queue, and pop the oldest element off. This leverages the fact that it's rare for two identical lines of text to appear in the chat window except in heads-up situations.
  • You can also parse each line of text as it comes in and see if it makes sense logically, according to the state of the current game. If you get a line of text "Player A bets 40" followed by "Player B calls 40" followed by "Player A bets 40" you can tell that the last message is redundant, because we never saw the text for Player C's action.

Also keep the following in mind:

  • Text output functions call each other! TextOut calls ExtTextOut internally, and DrawText calls DrawTextEx. Focus your hooking on DrawTextEx and ExtTextOut.
  • You can mostly ignore calls made with an HDC that correlates to a NULL window. That is, if WindowFromDC returns NULL, you can most likely ignore the call as these calls are associated with a screen DC, and a screen DC is not (usually) the kind of DC that people draw to in these situations. Instead, they'll draw to a window DC associated with the specific window.
  • Obviously, you can ignore calls the poker client makes with empty text.

You now have, believe it or not, everything you need to extract any piece of text from any application which draws text using standard APIs.

Conclusion—Parlor Tricks

I'd like to leave you today with one last picture. Look closely.

It looks like a foreign language version of the Internet Explorer "blank" page. This is what happens when we reverse the text passed to our detour, just for fun:

BOOL WINAPI Mine_ExtTextOut(HDC hdc, int X, int Y, UINT options, RECT* lprc, LPCWSTR text, UINT cbCount, INT* lpSpacingValues)
    if (!text)
        return TRUE;

    // Make a copy of the supplied string..safely
    LPWSTR szTemp = (LPWSTR)LocalAlloc(0, (cbCount+1) * 2);
    memcpy(szTemp, text, cbCount*2); // can't use strcpy here
    szTemp[cbCount] = L'\0'; // append terminating null

    // Reverse it..
    // Pass it on to windows...
    BOOL rv = Real_ExtTextOut(hdc, X, Y, options, lprc, szTemp, cbCount, lpSpacingValues);

    // Cleanup

    return TRUE;

Not too useful, but interesting, and illustrative. By interposing your code between the target application and the operating system drawing APIs, you can snoop on, modify, hide, replace, or cosmetically tweak the vast majority of text drawn on your system, in any process, any application whatsoever. This is how the images above (at the top of the article) were generated.

I hope I've made the point: extracting text from Poker Stars or any other application whatsoever is not only possible, it's easy. Let's add it to our toolbox and move on.

...emit txen litnU. sliated eht ni s'lived eht...rebmemer dna ,gnidaer rof sknaht ,syawla sA

Tags: Microsoft Detours, DLL Injection, poker bot, Windows API, GUI, online poker, poker

297 comment(s)

Brilliant work JD...

Really enjoyed the entire botting series.

Great article. Well written.

well worth the wait

A great series! Keep up the good posts!

Awesome post James. I look forward to more in the future.

Agree !

It was worth the wait !

Hope the 8 will come sooner than we think ;)

Amazing. I have enjoyed your poker bot series since day one. Because of you I am going to start my own bot... not for the financial aspects, but for purely academic reasons. I need a pet project to keep my coding skills sharp.

Does anybody know whether these same techniques work under WPF or is this strictly GDI? And is there any way to accomplish this purely in C#?

James: you should submit this over at one of the MS forums they would probably get a kick out of that last pic..

I'm curious whether or not these techniques can be performed using a language other than C# or C++. Like Java? Anybody?

Am I going mad or did 3 new articles just appear at once after a 2-week drought?! Very interesting as usual James.

you're going mad - these articles appeared days ago.

no, just kidding. but i think some guy in the previous post broke his F5 key, so maybe author felt bad about that and decided to release a couple extra posts. or maybe it was going to be another one of his endlessly long technical "code the known universe" diatribes and he decided to make good on his promise to start doing shorter pieces.

good set of posts though, except he chickened out and got rid of the boost. you people.. i swear.. what's so hard about building a couple hundred thousand lines of source code using an obscure nmake command and a twirling flux cogillitator? sheesh.

The term is 'flux capacitor' The cogillitator is for tranversing the interdimensional gateway, not time travel.

What I like about this post "Coding the Wheel" scrawled even on the system Start button. I was able to get it to work on my machine but I've shut my little app down and the text is still showing up on my Start menu. Weird. But interesting. Will have some fun with this one.

I downloaded your latest sample code and tried running it on pokerstars and it kept giving me errors. Namely, "The procedure entry point ?Detoured@@YGPAUHINSTANCE__@@XZ could not be located in the dynamic link library detroued.dll".

First question: Am I supposed to build this on my system or just open it (that's what I did) and if so - I've been trying to get a hold of visual studio but have been having lots of problems downloading from microsoft. any suggestions?

thanks! Great posts btw! This will open up a whole new area of programming for me.

Wow, they all came out at once! Great reading James, thanks.

Great series and I enjoyed each article very much. I hope now you've got a whole batch of articles off your chest you could get in touch about a possible advertising deal? has plenty of space to advertise this site for minimal effort on your part.

Cheers, Nick.

Nice, I really want to see this poker bot series continue. Solid articles for poker players, programmers, and poker bot enthusiasts alike.


I have been looking at this and it has me excited to program again. I used to program professionally, but stopped after taking a new position. So, this has me learning new things, re-learning old things, and generally excited to be in front of a compiler.

Thanks again.

Agree with chipset. I would like to see more managed code however, done in this style.

Does anyone know a good resource for hooking the graphics driver? I've heard this is possible but I've never seen an example of how it's done or what it might be used for. A few people over in the poker botting series were talking about this.

Hi dude, This is one great work you've done her. Impressive! keep on doing the good job!

I tried to look at many hooking examples, but none are compiled properly on VS8. I don't wanna sound too rude, but it would be great if you publish the code for the text replacement thingy.

Keep on doing that great job, RESPECT!


Nice, nice, nice. I was looking for this. Great!. I'm programming a bot on delphi for poker tables with RichEdit20W or similar controls that answer to the wm_gettext message. My hook only gets the hwnd of the RichEdit20W control and puts on my application(sometimes you go to another window with new controls and new hwnd.-obviously-, my hook capture the new hwnd if exist). My main application is out from hook .dll and capture all messages(the RichEdit control displays everything). I can parse everything. But, NOW, perhaps I can build the bot for the rest of poker rooms, thanks guy, you shown me a new way.

Great job!. PD: Awaiting your next article.

How would you hook the names of the other players and their chips counts?

I have a question that relate back to the foldbot. In the code you hard coded the X,Y coords to push the Fold button. My question is how did you came up with that? I looked at Spy++ but I didn't see much there that I could use. Was it a program you used or trial and error? Any help would be apprecaited!

The chips are the reason for I need this article, the names...Get all text, twice, @buffer1-@buffer2=new lines, parsing the new lines, example: 'Anonymous raise 600' pos:=ansipos('raise',sentence);//position of r playerstr:=midstr(sentence,1,pos-1);// name of player ... I put it on stringgrid, if the player action changes and player exist on stringgrid, I change the status of player. If not exist then add player to stringgrid. The name of players is not the problem, but the chips... yeah!. Then I found this article, that is a great job. If the detours working for me in delphi, dont need more. Ocr's and Screen scrap is difficult work but an option.

I forget it. My hook. I have a hook.dll and a hooklauncher with a button to start and edit box. The hook.dll drops the hwnd of the richedit control of the poker window in the editbox of hooklauncher, if window poker changes the hwnd changes(obviously, I know), but, my hooklauncher always have the same name then I have the main app, get the hwnd of hooklauncher window by name and with findwindowex gets the edit box control and with wmgetmessage gets the text of edit box. Drops the hwnd number in a memory file. The dll is not heavy. When I found this article my project was in advanced development, then only few changes have been implemented. In fact, I dont use the emstreaming capture method. This last article is very useful for me, and when my actual project ends, I will try the drawtext detour method that combined with x and y coordinates maybe tell to me everything.

Question: When I'm using Spy++ and the "Find window" and drops the finder tool on my poker clients chat window (I'm using Svenska Spel poker client) it shows nothing on Class and the Caption only displays the caption of the window the "chat box" belongs to. Any thoughts anyone?

I've had this same question. Is it possible for a window to have an empty or null class name? Jonny can you browse to the window in the spy++ list of windows?

I copy pasted the Mine_ExtTextOut code to reverse text into the code for the pstars / fulltilt bot. After I edited the OnBnClickedBtnStars function to allow iexplore.exe it did not reverse the text as expected, and as seen on your screenshot.

Could someone point out what I missed?

Fixed it!

I'm using a bot doing fix limit six handed and can't seem to come up with a winning strategy. I've done an interesting approach. I have the program playing the hand about one thousand times from that point on, for instance if I have pocket aces and 3 people are in the hand it will find the odds that I will win based on one thousand hands. So I am able to come up with a percentage as to whether or not i should play the hand. I still can't win efficiently over 1000 hands at the lower limits. Any advice?

Thanks for the response James. What are possible ways to see the villain's range? Doing it the way i've done I can stay alive in a room for hours without going broke but it is a very slow loss of money as time progresses. I'm guessing other players see how tight I am and play against me accordingly. Right now i'm trying to calculate the other players agression factors, V$IP, and PR, then im going to modify my range to either call or reraise the weak players who are just cont. betting me. When can we expect an article on this meaty stuff?

hi, you honestly rock! thanks for your botting series so far!

can anybody help me with this issue? i'm searching a windows api call, which is called everytime a new window is created (e.g. for getting a new opned pokertable). i dont want to use the cbthook. i already tried to hook CreateWindowExA(and W), but the poker application kind a crashs (on ub). also i tried to hook EnableWindow, but it seems that not all new opened windows are shown (it's weird, only no limit tables are shown). ohter hooks i tried are showwindow and registerclass. so if somebody have any suggestions, pleas let me know! thanks

hi james, yea well i'm using the exactly the same from the traceapi sample of the detour project. here my code i'm testing: [url][/url]. ub won't start at all wenn i attach both of these calls and if i only attach the createwindowexa call to detours, ub starts but fails early with an unhandled win32 exception. it's really weird, but i'm looking forward you could give me a hint. thanks!

This is great stuff. You must have considered rolling all this into something that we can pay money for?

I'm primarily interested in limit poker AI and do all my testing against the Poker Academy bots. I've never actually tried to hook anything up to a real poker site so what I'd really like to see is some middleware (with or without it's own bots) that handles all the data extraction and user click emulation and which provides me with a slick interface where I can insert my own poker playing logic (C++ in my case).

I'd be happy to good pay money for this and I'd also be happy to pay for regular updates to keep pace with any changes that poker sites might make to their client applications.

From the response you've had to the articles so far, I would guess that if you throw some half-decent bots into the package you'd have rather more people than just myself banging on your door with credit cards in hand!

For me as a computer geek and a poker player it's been a set of most interesting lessons on a great topic and great techniques. In fact I can't remember any programming topic ever that captured my interest as much as this has. Great work James. Every day I look for more...

What are some ways that people are making there bot realize that its there turn to act? My first idea was keep track of who is on your left but this starts to become a mess when people stand up or are not in the hand.

This is what i am thinking of using now.

string playerToLeft = ""; int playerToLeftIndex = 0; for( int i = 0; i < newGame.Players.Count; i++ ) { if( newGame.Players[i].Name == currentPlayer ) { break; } if( newGame.Players[i].IsInTheHand ) { playerToLeftIndex = i; playerToLeft = newGame.Players[i].Name; }

                            if( playerToLeft.Length > 0 ) {

Anyone get this working on Vista 64?

Yes its working on vista 64 for me. Let me know if you have questions.

hi, i have another problem on ub. everytime textouta is called to give out a dealer notification in the chat window, the hdc adress is always different. and it's also weird that the WindowFromDc HWND handle is always 0 then. i would appreciate it, if anyone could help me. thanks!

Hi all !

Great articles James

For those of you who prefer C# and are struggling with Dll Injection and API Hooking, take a look at EasyHook ( Download the binaries, read the small doc, and youre on your way injecting dlls and hooking from managed code.

I did preliminary tests and it seems to be as good as promised


Very interesting work!!

I have starting doing my homework with a PokerStars bot. One question for any people working on same subjet:

What is the best way to get accurate information about position and stack of each player around the table. The information is given in the history file :

Seat 1: player1(1500 in chips) Seat 2: player2(1500 in chips) Seat 3: player3(1500 in chips) Seat 4: player4(1500 in chips) Seat 5: player5(1500 in chips) Seat 6: player6(1500 in chips) Seat 7: player7(1500 in chips) Seat 8: player8(1500 in chips) Seat 9: player9(1500 in chips)

but it may not be 100% accurate in case a player move to another table. The only place to get 100% accurate information seems to be the table window itself but It did not seem to call DrawText or TextOut to display it.

Thanks Fab

I made my own working bot for PokerStars, just run and enjoy!

In Part-6 there was a blurb about Part-8 already being written... but it's been almost a month since then.

Is this series going to continue?

That's what I'm wondering...


  • ¿Because detoured.dll create your own, because we do not use that comes with Detours ?
  • ¿Detourcreatewithdll have to be in a dll.?


I have followed your BOT expositions since the beginning, and even through the BOOST phase, successufully, ( and I am not a strong programmer). You have even inspired me to learn C++.

Since we haven't seen an article for a month now, I am beginning to wonder if something has discouraged further discourse on the subject.

Please let us know all is well, and PART 8 is on the way.

Hey , great Work so far to bad its so recent and all the articles of the series are not yet published. I look forward to your version of a trashbot. i have tried many attempts of making the foldbot a trashbot but my skills are nowhere near what i would like theme to be in c++. Anyway keep up the good work


Thank you for this article, but my english is very bad and my only known language is c#. I understand the logic behind it but i cant do anything in c++. It will be very very nice if somebody compile a dll to use in c#.


I've some Questions. I hope someone know the solutions. I work with Poker Stars.

1 : I send over SendMessage a MouseClick on the Buttons to Call, Raise etc...

It work well but only if the TableWindow is bigger or equals the beginning size. If i change the size to smaller by resizing the TableWindow nothing hapend if i click with SendMessage on the Buttons. I have checked the x, y Coordinates by Drawing a Point where i click over SendMessage.

2 : I need to know the Chips and the Player Count on the Table. And what is importent my Cards. With this Methode i only gets the Bets of my Opponents.

I hope someone can help me.

I found a Solution for the #2 Problem. PokerStars logs the hand in realtime in the PokerStars.log.0 But the first Problem is still open.

I'm not seeing DrawText, ExtTextOut, etc functions being called to draw player names, balances, etc. Just calls to output text to the chat window. Am I missing something? How could you make the first image above?

And just to clarify my question above... I'm not filtering based upon window classes. I'm looking at every call FullTiltPoker.exe makes to the DrawTextEx and ExtTextOut functions, and don't see anything for player names, balances, etc.

First I want to thank you for the tutorials here; really great stuff!

I tried your code on Titan (IPoker Network). The function ExtTextOut shows the dealer messages. But when I tried to get the HWND WindowFromDC(hdc); it returns NULL. Then I enumerated all child windows of the table and know that the last child is the chat window. Is there a possibility to only hook into that child window?

Thanks, Roy

First of all, on Poker Stars, don't worry about it. We can bypass redundant messages thanks to a quirk in the drawing logic. This technique was demonstrated in the source code accompanying How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt.


I'm again. You said something about technique fix the Problem with the redundant messages. I searched in your Source Code for the Solution. In witch File i can find it ????

mfg ayke

In my detour of the ExtTextOut-function I do this: HWND hWindow = WindowFromDC(hdc);

It sometimes returns NULL for me and sometimes it works. For example it works in FullTilt chat-window, but only on some controls on the lobby window (works in the column headers but not on the rows of the table). MSDN says it returns NULL when control is not attached to any window. As a win32 n00b I do not undestand what this really means. All I know is that the IPoker-client I'm detouring it is not working and I do not know any other approach.


We find our hole cards by parsing PokerStars.log, but how do we determine which table the hole cards belong to. If I have three PokerStars tables running, all them write to PokerStars.log. In the example below is 001F01C6 the table handle?

MSGTABLEPLAYERCARDS 001F01C6 ::: 8d ::: 13s

Is 0201F060 a sub window of 00260100..

[2008/09/01 23:30:38] MSGTABLEADVANCED_BET 00260100 Table::AdvActions::config() 0201F060

Thank you in advance for the clarification.

Correct, Use Spy++ or a similar tool and you'll understand.

" In the example below is 001F01C6 the table handle?

MSGTABLEPLAYERCARDS 001F01C6 ::: 8d ::: 13s "

Yes it is. If you want to know the HWND object, you could grab it as a CString and then convert it to a number. I made it yesterday and it works - although casting to a HWND raises a warning:

CString handleS = _T("001F01C6"); ULONG handleL = wcstoul(handleS, NULL, 16); HWND handle = (HWND)handleL; //<--Now you have the handle!!

hello James, First of all i want to say thanks for inspiring me... you did a better job then my teachers at the university :) thanks to your posts i' m learning a lot....

As Dave asked, can you please tell us which API method did you use to ovverride player's names, balances, etc....

I have tried my shareware programs that monitors which APIs are called from a process, but none of them works how it should... do you have any suggestion?

thanks a lot, James and good luck for your blog.

I tried adding Poker Academy Pro support. I can launch PAPro, and the function calls show up - but PAPro hangs before the splash screen and just sits there. I at least expected it to run OK even if I didn't get the expected results. Anyone tried to work on Poker Academy?

Nevermind, just seems to be a little unstable.

I wasn't able to get any text out of Poker Academy so far unfortunately. Any hints on what calls to hook into? History files, GDI texts? How does a Sun AWT frame output text?

Titan is kind of interesting, because it seems you have to assemble table infromation from a finished hand's log first, and then apply the dealer chat for deltas. Any other way for Titan to create a table state structure? Any way to get to the text written to the buttons or the seat lables for example?

Addendum: Found the Java API for Poker Academy. THat seems to be an easy way to interface it...

Fortunately I built my BotBrain - Client interface over a socket connection, so it doesn't matter whether the client interface is written in Java or C++.

Anyway, any smarter way to interface Titan Poker other than reading the dealer chat for current events and reading the hand history WriteFile call for table state?

Thanks for great articles. These have taught me several new tricks I didn't knew even possible (like Detours). And I'm a professional programmer and have been working on a poker bot project on/off over a year. Also, you can write very well, easy to follow.

I would like to share some knowledge about OCR. And by this, I mean like taking screenshots of poker client window and analysing all the information from the bitmap. I've coded a program that can do this, although it's not real OCR, but just comparing character pixels to already known comparison pictures. You have pointed at some article, how difficult OCR can be, and I can say you're quite right. All possible characters have to be gathered from the poker client to a separate comparison bitmap. I have done this to one poker client, and the amount of characters is as big as 150, because all font sizes, capital letters etc. have to have seperate pictures. On the plus side, table and hole cards can be captured using this same method.

Some things considering speed. I first thought the speed would be problem with OCR, because the pixel comparison algorithm is quite complex. However, I'm a bit surprised how fast it is. After all, real changes doesn't happen so often in poker window. 90% of the time it is enough just to take a small screencapture from the message box to see if nothing has happened since last capture. When the whole table is completely captured (messagebox, table cards, all players names and stacks, player cards) it takes about 5 ms to take the screenshot and 25 ms to analyze with my 2.13 GHz processor. And this is C# code. I have also a c++ algorithm that is better optimized in many ways, which manages to analyze in about 10 ms.

How I see OCR: +to my opinion, no way to get caught, because screenshots are the only way used to read information +fits to all types of poker clients whatsoever -hard to code, lots of debugging -lots of work with every new poker window type -takes a bit CPU time -a little delay before information if gathered

Hey John,

you don't happen to have been studying computer science in Southern Germany with a room-mate named Jan in the appartment in the first year, do you? Otherwise I assume we've been coding some network stuff (starting with a serial 38,4 k connection, eventually moving onto an ARCnet drilling holes through the walls) together...

Hi all,

I've been looking at this on and off for a while as I've been planning on making a "Poker Bot". However, mine would really act as an "auto-folder". I wanted a program which read my hands in real time, worked out the current position I'm in and then read from a list of hands I wish to play. If the hand doesn't exist then fold then hand automatically for me. I finally achieved this. :)

The program I coded only works for PokerStars as that is the only poker software I use, but if any one is interested I could post some code on how I achieved this. (It is written in C# however). I didn't use an DLL injections or read the window text etc, and I've been using it for around two days now and it works great.

I retrieved the hands from the PokerStars log file, worked out how many players etc are playing and on the current table etc all from this file. However, I manually have to type in one thing, and that's where my player is sitting on the table. I can't see where to get this information from, other than reading the text in the window, but didn't want to go down that route just for one small problem.

Great Post!

"First of all, on Poker Stars, don't worry about it. We can bypass redundant messages thanks to a quirk in the drawing logic. This technique was demonstrated in the source code accompanying How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt. "

sorry to say, but it's not true. as you can see Poker stars MonitorBot duplicates messages from the chatwindow when some one wins a hand:

Dealer: Game #2118426xxxx: PLAYERNAME wins pot (9700) Dealer: Game #2118426xxxx: PLAYERNAME wins pot (9700) Dealer: Game #2118426xxxx: PLAYERNAME wins pot (9700)

I didn't find any code snippet that handle this in the MonitorBot source code.

am i missing something?

In reply to Jay (7/18/2008). I know that comment was a long time ago, but I have been struggling with the same problem and it was driving me mental. Everything worked on one pc, and I kept getting a very similar error as Jay ("The entry point could not be located in the dynamic link library detoured.dll") on a second pc. Same visual studio, same project, same build settings etc.

I finally figured it out after searching my machine for other instances of detoured.dll - there was a program which I guess was doing its own bit of detouring already (?) "Dell\EMBASSY Trust Suite by Wave Systems\Embassy Trust Suite\Document Manager Lite" when I ran setup, uninstalled it and restarted my pc, magically - the FileMonitor sample worked.

Just putting this out here in case anyone has the same problem and is pulling their hair out like I was.

Hi, maybe a bit late for this but... I am trying to hook into Poker Academy (PA) as some others have on this thread (Jan and M). I am having similar problems with hooking the standard apis for windows - DrawText etc - that were used in the examples which do not seem to be called by PA and also figured that it must have something to do with the fact it is java. Jan mentions "Found the Java API for Poker Academy". Is there anyone here that has successfully hooked to this and is getting information from PA? If so can they elaborate on the specifics? Is there any way to find what apis a program is calling? Is this a stupid question because should spy++ show this information? Thanks for any help.

So much scrumptious code, no but seriously and awesome multi-post article on creating a poker bot!

i made it! finally i arrived to do a poker robot. If you want to try it or have a look on it, I put it in my website. Enjoy!

Has anybody written code to get text from the pokerstars lobby?

I simply wish to get the text in the listview of the tables, ie table name, limit, stakes, players so I can make an automatic table opener.

Any clue as to how to do this, anyone?

I'm trying to get text from titan windws too but it semms to use the mentioned apis only in few parts ...the main part of text isn't captured by our hook... Someone solved this problem ? wich api titan poker uses to print text to screen?

Amazing series of articles, just from the point of a programer.

I have spent a lot of time on global system hooks and hook-injection after reading your articles. But there is one important question to be answered:

If i place my code between target application and operating system, wont the c++.dll will be loaded in every application? So wont for example pokerstars will be able to checksum the dll-manipulation?

I have found information on loading dll before let the target application generate its initialize-compare-checksum, but how is your solution working not to be detected?

Thx in advance.

I'm trying to modify the XMonitor code to be able to monitor an ipoker client.

Does anyone know how to handle PTIODEVICE windows and get caption from them? Does anyone know how to get the text from the chat ?



I tried the API-Hooking method with the uallCollection for Delphi on PartyPoker. It works, but after maybe 20 seconds, PartyPoker is closing the table and I'm back in the lobby! Do you know if there is something like a detection method against API-Hooking? Maybe there is also some error in the code that forces PartyPoker to close, but all the other applications like Firefox etc. are working stable!

Hi all, also for me the window retrieved by WindowFromDC is ALWAYS null, and so I'll never know which window has called the TextOut (or DraText) API. Any suggestion? Anyone with the same problem? Thanks in advance. Just a question, is the hdc valuable outside the process that has created it?


Nice job!

We did something similar too, you can download the free trial and check it out!


You can use eazy way to get message from chat window.

1.Call SendMessage function to send mouse event to select text that you want. 2.Use SendMessage function to send Ctrl+C key event that poker stars will copy text into clipboard. 3.Use hook and inject your code into SetClipboardData function and take chat text.

detoured.dll was not found

I get this error when trying to open one of the poker clients!

any help on this one?

XPOKERBOTHOOK_API InstallHook(LPCWSTR pokerClientPath, bool installCBTHook)




this fixed my issue

Although this is a different implementation, it reminds me of the OPenGL hacks used for CounterStrike, such as seeing through walls, etc..

I wonder where your ideas come from...

They must be a combination of a deep rooted curiosity and an insatiable urge to discover what is possible. Anyways, it's a great quality to have, but as I said before, I would like to see your intelligence/genius applied to other fields and blogged about. Lots of things can be reverse engineered.

This is pretty sick, I didn't know like 10% of what's written here. Kinda scares me that there may be some whiz kids trying to get an edge in this zero sum game.

Well I am using Vista, and when I detour both text functions on Poker Stars I can replace and edit the text, but not the text of the chat window.

I think Poker Stars has smartened up and taken another approach to drawing the chat window text.

That or it is vista. Anyone have evidence contrary to this?

those images rocks, your unbelievable!

Great job

I also cannot monitor the chat by detouring the text draw functions in the way described above in Poker Stars using Vista... Are there any other possible functions which I could detour to have more success?

Hi everyone,

I would like to congratulate James Devlin because your web site is really amazing. From the technical point of view, it is 7 stars, not 5. It is outstanding.

Right now, I would like to find someone to cooperate with me on a poker software project whose goal is to improve SNG table selection (Pokerstars). As James Devlin states, table selection is very, very important and I need to improve that area. What I am looking for is someone that is able to develop a software that retrieves the content of any listbox on Pokerstars. I have already developed all the software needed to evaluate if we should or should not register on each specific tournament taking into account some statistic criteria ....

If you are interested, please post your email on a comment and I will contact you afterward.

Hi james. Fisrt of all I would like to thank you for all those wonderful articles !

But I have a problem. The same problem as Jeff and John, that is to say that I have successfully detoured the function DrawTextEx and ExtTextOut(and DrawText and TextOut to be sure :) ) and some of the information in pokerstars or fulltilt are well changed by my own function, but the chat (mainly) is not changed at all. I was so wondering if it exists other function that I should detoured to get the chat text. Thank you one more time for your articles :)

Hey Poker of Aces, I work on another Poker project. I focus on cash games. But maybe we could exchange some knowledge and how to investigate new Poker rooms.

Feel free to write me an email:

is it possible to build a bot for facebook texas holdem to win[url=]facebook poker chips[/url]

I would still like to know what Poker Rooms the technique works more effectively on? And how much testing have you done with [url=]Rakeback[/url] sites?

What a amazing idea.

To all those people asking stupid questions like "is it possible to create this bot on facebook". Did you read anything or are you just one of those lazy, silver spoon fed, wastes of oxygen? This guy takes all this time to write an amazing read and the only shit you can ask is that? GO AWAY.

To those of you who struggle trying to extract the chat text from the FullTilt software, know that since some months ago, their software is built using QT (v4.5.2 if I remember correctly).

QT is an application and UI framework available here:

Since you can download their SDK (c++ source files) and access all the documentation, It becomes very easy to reach our goal.

In a nutshell, our injected dll should: 1- find the chat window's handle 2- call QT's QWidget::find() to get a QT Widget object from the handle 3- cast it to a 'QListWidget' 4- check the 'items' property Bingo ! you get all the text in the chat window !

Use the form below to leave a comment.

Coding the Wheel has appeared on the New York Time's Freakonomics blog, Jeff Atwood's Coding Horror, and the front page of Reddit, Slashdot, Digg.

On Twitter

Thanks for reading!

If you enjoyed this post, consider subscribing to Coding the Wheel by RSS or email. You can also follow us on Twitter and Facebook. And even if you didn't enjoy this post, better subscribe anyway. Keep an eye on us.

Question? Ask us.



Coding the Wheel =
Code, poker, technology, games, design, geekery.


You've read our technical articles, you've tolerated our rants and raves. Now you can hire us anytime, day or night, for any project large or small.

Learn more

We Like

Speculation, by Edmund Jorgensen.