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

Introduction

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.

Hey, did I mention that VIRTUALLY EVERY PIECE OF TEXT ON THE SYSTEM IS ROUTED THROUGH A HANDFUL OF TEXT-OUTPUT APIs?

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)
    {
    case DLL_PROCESS_ATTACH:
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText); // <- magic
        DetourTransactionCommit();
        break;

    case DLL_PROCESS_DETACH:
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)Real_DrawText, Mine_DrawText);
        DetourTransactionCommit();
        break;
    }

    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..
    wcsrev(szTemp);
   
    // Pass it on to windows...
    BOOL rv = Real_ExtTextOut(hdc, X, Y, options, lprc, szTemp, cbCount, lpSpacingValues);

    // Cleanup
    LocalFree(szTemp);

    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


Posted by James Devlin   58 comment(s)

SEARCH

COMMENTS

Brilliant work JD...

Really enjoyed the entire botting series.

LogicalAI on 7/17/2008 12:42:41 PM (41 days ago)

Great article. Well written.

Anonymous on 7/17/2008 1:17:23 PM (41 days ago)

well worth the wait

Anonymous on 7/17/2008 2:28:05 PM (41 days ago)

A great series! Keep up the good posts!

Anonymous on 7/17/2008 2:30:08 PM (41 days ago)

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

Andrew G. on 7/17/2008 2:36:12 PM (41 days ago)

Anonymous on 7/17/2008 3:15:44 PM (41 days ago)

Agree !

It was worth the wait !

Hope the 8 will come sooner than we think ;)

Anonymous on 7/17/2008 3:21:47 PM (41 days ago)

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.

Trace A. on 7/17/2008 4:18:50 PM (41 days ago)

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

Ed K. on 7/17/2008 7:23:01 PM (41 days ago)

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

Jam Ends Evil on 7/18/2008 4:29:30 AM (40 days ago)

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

Paul on 7/18/2008 5:53:50 AM (40 days ago)

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.

Anonymous on 7/18/2008 8:11:20 AM (40 days ago)

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.

JeremyX was here on 7/18/2008 8:27:16 AM (40 days ago)

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.

jay on 7/18/2008 11:21:11 AM (40 days ago)

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

Poker League on 7/18/2008 12:30:04 PM (40 days ago)

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? http://www.pokerisrigged.com has plenty of space to advertise this site for minimal effort on your part.

Cheers,
Nick.

Poker League on 7/18/2008 12:35:58 PM (40 days ago)

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

http://www.icmbot.com

Poker Bot on 7/18/2008 10:51:38 PM (40 days ago)

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.

chipset on 7/19/2008 11:36:30 PM (39 days ago)

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.

Anonymous on 7/20/2008 5:37:41 AM (38 days ago)

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!

Benda

Benda on 7/20/2008 10:20:24 AM (38 days ago)

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.

Berto on 7/20/2008 8:04:58 PM (38 days ago)

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

Anonymous on 7/21/2008 6:11:53 PM (37 days ago)

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!

Adam on 7/22/2008 11:01:13 AM (36 days ago)

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.

Berto on 7/22/2008 3:43:22 PM (36 days ago)

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 wm_getmessage 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 em_streaming 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.

Berto on 7/22/2008 5:36:06 PM (36 days ago)

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?



jonny on 7/23/2008 5:08:03 PM (35 days ago)

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?

Keith on 7/24/2008 9:25:59 AM (34 days ago)

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?

KS on 7/27/2008 3:09:37 AM (31 days ago)

Fixed it!

KS on 7/27/2008 12:36:23 PM (31 days ago)

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?

Anonymous on 7/29/2008 12:53:53 PM (29 days ago)

Read HPFAP (Holdem Poker for Advanced Players), apply the ruleset, hook your bot up to Poker Tracker, and look at way more than 1000 hands?

1000 hands isn't a large enough sample set for anything (whether computing equities or measuring your own results) but even if it were, the "my hand is X% to win against 2 random hands" statistic is only useful as a general indicator. What you really want is to assign a hand range to your opponents, compute the equity, and refine that range as the hand progresses.

Even then, a lot of the play is tactical (I've flopped top two pair, so I'm going to go with this hand regardless of the equities) rather than strategic (I'm an X% favorite vs. villain's range).

We'll be discussing this in detail so stay tuned!

James Devlin on 7/29/2008 5:25:36 PM (29 days ago)

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?

Gen_Poker on 7/29/2008 7:13:40 PM (29 days ago)

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

Anonymous on 8/1/2008 12:13:32 PM (26 days ago)

Gen_Poker: How are you making the determination to keep/fold a hand? Preflop, and on/after the flop? Example: Villain (TAP) raises UTG, you 3-bet with QQ. All fold. Flop A K 2. Villain bets. What does your bot now do and why? Are you already incorporating PT stats? And can the bot compute hand vs. hand range equities for multiple opponents?

Anonymous: Can you post your CreateWindowExA (and W, both versions) detour code?

James Devlin on 8/2/2008 12:26:03 AM (26 days ago)

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: http://rafb.net/p/ucBp4P19.html.
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!

Anonymous on 8/2/2008 3:42:16 AM (25 days ago)

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!

KayEmm on 8/2/2008 9:54:25 PM (25 days ago)

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

DM on 8/4/2008 4:45:23 AM (23 days ago)

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.

LastChance on 8/5/2008 11:02:31 AM (22 days ago)

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 ) {

LastChance on 8/5/2008 11:40:12 AM (22 days ago)

Anyone get this working on Vista 64?

Paul on 8/5/2008 7:23:08 PM (22 days ago)

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

LastChance on 8/6/2008 3:36:20 PM (21 days ago)

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!

Anonymous on 8/7/2008 3:00:22 PM (20 days ago)

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 (http://www.codeplex.com/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

FreakNguyen on 8/12/2008 8:27:48 PM (15 days ago)

Hey,

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

Fab on 8/13/2008 9:44:30 AM (14 days ago)

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

www.torrentparty.com/...f5e64b6d0601af9f54360.html

Anonymous on 8/13/2008 11:28:53 AM (14 days ago)

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?

DM on 8/13/2008 1:32:35 PM (14 days ago)

That's what I'm wondering...

Anonymous on 8/13/2008 4:28:06 PM (14 days ago)

hi,

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

alex on 8/14/2008 2:31:51 PM (13 days ago)

James,

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.

James on 8/14/2008 4:49:37 PM (13 days ago)

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

Mark on 8/15/2008 10:17:12 PM (12 days ago)

Hi,

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#.

Jim on 8/20/2008 10:26:09 PM (7 days ago)

Hi,

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.

ayke on 8/22/2008 8:13:13 PM (5 days ago)

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.

ayke on 8/22/2008 8:42:00 PM (5 days ago)

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?

dave on 8/24/2008 10:53:33 AM (3 days ago)

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.

dave on 8/24/2008 10:55:35 AM (3 days ago)

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

Roy on 8/24/2008 3:22:03 PM (3 days ago)

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.

Hi

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

ayke on 8/25/2008 4:13:17 AM (2 days ago)

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.

JMK on 8/27/2008 9:30:45 AM (15 hours ago)

Comment on this post:

Thanks for your interest in Coding the Wheel. All fields are optional.