How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt
Thursday, July 17, 2008   

Introduction

Since this series began, I've been asked one question over and over.

How do I build a poker bot for Poker Stars and/or Full Tilt? How do I extract text from the game chat window? How do I snoop on hand history and log files as they're generated?

Today we're going to answer that question.

As of this writing, Poker Stars is the most popular poker venue not only in the world, but in the history of poker. Online or brick-and-mortar, it doesn't matter. There's not currently, and there never has been, another card room in which so many people congregate so routinely to play poker for such sums of money. If you took one of the largest buildings in the world—for example, the Lockheed-Martin manufacturing plant in Fort Worth, Texas...

...and filled it with tables, stretching off into the distance, you'd have yourself a casino which could handle maybe 1% of Poker Stars traffic. Maybe 5% if you pack them like sardines.

With well over 120,000 players at peak hours, Poker Stars isn't really a card room at all.

It's a small city.

Controlled by a mysterious group of investors, operating behind the veil of privacy.

Nothing wrong with privacy; given the politics surrounding online poker, I positively understand it. But in the absence of hard information, rumors abound: Poker Stars is owned by a group of top poker players; a cadre of eccentric billionaires; foreign governments; you name it. I challenge anyone reading this (other than Poker Stars staff and other industry insiders) to answer the question:

Who owns Poker Stars? Who owns Rational Entertainment Enterprises, Ltd.?

Because that's a tough nut to crack, when you're on the outside looking in. And inquiring minds want to know.

Building a Monitor Bot for Poker Stars and Full Tilt

Anyway, we're going to build a little application which is capable of extracting complete table and game information from the Poker Stars client. Hole cards, board cards, player actions, you name it.

We'll call it the "MonitorBot" although it's not technically a bot since this one (unlike last week's FoldBot) doesn't actually click any buttons. Here's what it looks like. Nothing too snazzy as you can see...

Did I mention that one thing today's bot will not do, is click any buttons on your behalf? It's only purpose is to gather information. Because it doesn't automate your play, use of this tool does not constitute a violation of typical online poker EULAs. In fact, all of the information retrieved by this tool can be extracted by publically available tools such as Spy++ and others.

But notice that we're displaying game chat text (in real time) in the top right window, and notice that we're displaying log file text (in real time) in the middle window. Getting access to this text cleanly can be a little tricky. 

The Techniques

Several techniques are demonstrated in today's article. I've broken these out into separate posts.

Then in Part 8 (coming sooner than you think) we'll take these techniques in a direction you might not have anticipated. It should be interesting. Stay tuned.

Running the Poker Stars MonitorBot

In order to run the above application, you'll need to do two things:

  • Download and build the source code (and this time, no Boost libraries! And no NMAKE!)
  • Download and install Poker Stars (www.pokerstars.com) and/or Full Tilt (www.fulltiltpoker.com) and set up a play-money account.

Once you've downloaded and installed Poker Stars, you'll need to set your dealer message verbosity to "Everything" as shown here: 

Repeat the above step on Full Tilt. Then follow these steps to see the MonitorBot in action:

  1. Launch the Monitor Bot executable (XPokerBot.EXE).
  2. Click the "Launch Poker Client" button and browse to the location of the main Poker Stars executable. On most systems, this will be in C:\Program Files\PokerStars\PokerStars.exe. Alternately, browse to Full Tilt.
  3. You should see the Poker Stars client launch. Messages will start to appear in the MonitorBot user interface.
  4. Open one or more Poker Stars game tables. If the tables don't appear in the MonitorBot UI, jiggle the table window by bringing the MonitorBot UI to the foreground, and then bringing the poker table window to the foreground, etc. This is due to a quirk in the underlying Window detection logic.
  5. (Optionally) open the Full Tilt client (FULLTILTPOKER.EXE) using the same "Launch Poker Client" button, and open some Full Tilt tables.

Limitations

The MonitorBot is a demonstrative application, not a working poker bot. As a result:

  • It only works with Texas Hold'em tables on Poker Stars, Full Tilt, and Poker Time.
  • It only works with Windows XP. It may or may not work on Windows Vista.
  • MonitorBot doesn't display hole cards in the UI like the FoldBot did. Instead it demonstrates how to pluck these cards from the log file and/or game text windows.

It's intended to show you techniques which you can use in your own applications, or to serve as a "starter kit" which you can embellish.

Hey! Where are the hole cards?

Different online poker clients emit different information into the game chat window. Some of them, such as Poker Time, display your hole cards in this window; others, like Poker Stars, don't. But in that case they may emit hole cards into the log file (as on Poker Stars) or into the hand history file (as on most poker clients, although there are timing issues).

And in any case, there are other ways to go about getting the hole cards. The point of today's post is to show you one way (out of several) to access the three basic types of text emitted by the client...

  • Game chat window text.
  • Log file text.
  • Hand history text.

Once you have access to that text, what you do with it is up to you.

Building the Source Code

This week's project is much easier to build than the FoldBot we looked at last week.

  • No more Boost! By popular demand, clean regular expressions are gone in favor of low-level, error-prone, but considerably easier-to-build string manipulations.
  • No more NMAKE! This project uses the Detours library, but I've included separate Visual Studio projects for detours.dll and the Detours marker DLL.

So what are you waiting for? Download the MonitorBot source code (C++/Windows 157KB).

Conclusion—Where's the AI?

As interesting as inter-application command and control techniques can be, I think you'll find the A.I. aspects of bot-building even more interesting. We've been busy laying out the framework for the bot's eyes (input) and hands (output) but we'll return to the A.I—the brain—before too long.

For those of you who are new to poker botting, we'll discuss how to build an A.I. from scratch, and we'll talk about how to leverage existing commercial bots as programmatically callable components inside your "master" bot. And whether you're new to poker botting or not, we'll discuss hand evaluation and comparison, game theory and the nuts and bolts of ICM calculation, rules-based vs. heuristic approaches, the importance of table selection, and much more. And at some point we'll say hello to some of the other communities and poker-programming sites that are out there, commercial or otherwise.

Until then, good luck in your poker and programming endeavors.


Posted by James Devlin   174 comment(s)

Need help! I' m enamored of J.D. Wink!

popo on 7/17/2008 11:09 AM (601 days ago)

J.D.: There is a little mistake...in "The Technics" you are linking Part 7 twice.
c u

popo on 7/17/2008 11:47 AM (601 days ago)

I am impressed!

Anonymous on 7/17/2008 1:14 PM (601 days ago)

Thank you for eliminating the need for NMAKE. It was kicking my butt.

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

Thank you, looking forward to your next article.

I've compiled, and tested, works like a charm. And just now started meddling with the code. (still have to wrap my head around some c++ it seems)

I'm really looking forward to some robust IPC examples.

Keep up the good work

Nyx on 7/17/2008 1:35 PM (601 days ago)

Hmm. Tried this one on Windows XP. I can see the messages pass in the Function Calls window, but I never get the games to show up. I have tried wiggling the windows. Strange.

chipset on 7/17/2008 1:53 PM (601 days ago)

finally!!!!!111

Anonymous on 7/17/2008 2:23 PM (601 days ago)

So far so good. w00t.

Andrew G. on 7/17/2008 2:43 PM (601 days ago)

Great work. That compiled and worked 1st time.
Is there any change of making a .NET managed wrapper of the non C++ peops out here?

Anonymous on 7/17/2008 6:58 PM (601 days ago)

You have done an awesome job writing this tutorial! I look forward to your future posts!!!

Paul on 7/17/2008 7:39 PM (601 days ago)

Amazing with a capital "A"

It compiled on the 1st try for me too. And ran perfectly, as adverstised (once I identified and found the executable!).

What is the danger of running this in the open? James wrote:

"Because it doesn't automate your play, use of this tool does not constitute a violation of typical online poker EULAs."

but the README file contains...

"Shut down the XPOKERBOT and POKERTIME applications completely before playing real-money online poker."

Anonymous on 7/17/2008 11:41 PM (601 days ago)

"What is the danger of running this in the open?"

Only Poker Stars and/or Full Tilt can answer that.

Björn on 7/18/2008 12:50 AM (600 days ago)


Not only does MonitorBot work, the source code is a thing of great beauty and precision. It reads like poetry.

James Devlin is for real.

BotEnvy on 7/18/2008 1:10 AM (600 days ago)

I agree.

G r e a t E x a m p l e !

Question for anyone who knows:

How would a bot determine player position?
One way that I can think of is reading the previous hand history file, and incrementing the button, but that has some obvious problems, with players leaving, joining, and sitting out, so that can't be it. Thinking out loud... probably by parsing the game chat text, which shows who posts BB and SB, and shows player actions in order. Is that the way to do it?

Also interested in seeing such basics as calculating pot odds, and considering opponent history of loose/tight and pre and post flop aggression

This site delivers

Urbane Plowboy on 7/18/2008 2:21 AM (600 days ago)


The one thing that I'm still fairly fuzzy about is how the Global CBT hook relates to detours text extraction. Someone please correct if I'm wrong on this...

The Global CBT hook detecs process windows
The Detours DLL_Process_Attach has nothing to do with that, and will work regardless?

Gamer on 7/18/2008 3:45 AM (600 days ago)

@Gamer - in order to extract the text, you have to put your code in the poker client's process. One way to do that is with CBT Hooks; CBT hooks are *also* useful for detecting when a window is created/destroyed. But you could inject the DLL and do the Detour text extraction without the CBT hook if you wanted.

JeremyX on 7/18/2008 8:02 AM (600 days ago)

Gr8 with new articles Smile

I tried sending the chat to a simple C# program (redirected the transmittext to send to my own window) the problem i ran into is that i cant seem to decipher this struct:

struct GAME_TEXT
{
GAME_TEXT()
{
::ZeroMemory(this, sizeof(GAME_TEXT));
}

EPokerVenue Venue;
HWND Window;
wchar_t Text[255];
};

into a working C# struct, tried this:

[StructLayout(LayoutKind.Sequential)]
public struct GAMETEXT
{
public int Venue;
public IntPtr Window;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
public string Text;
}

but i only get the first character in the chat... anyone know how to this properly?

H4mm3rHead on 7/18/2008 9:36 AM (600 days ago)

Urbane Plowboy wrote:

"interested in seeing such basics as calculating pot odds"

I think that can also be obtained by parsing the game chat text, which details all money put into the pot for each hand. Keep a running total of money in the pot for each hand. The current pot odds would be (total money in pot) / (amount of current bet or raise to you).

another topic would be Reading the Board: Flop, Turn, River
"Texture" of board, possible flush, straight, board pairs, overcards to your hole cards, etc. and then using that info to influence your actions (via some sort of multiplier).

as well as opponent tendencies to play tight or loose, aggressively or passively, steal blinds, defend blinds, continuation bet, etc. etc. etc. (once again via some sort of multiplier, while still evaluating the board)

don't know how much of this sort of thing is necessary or achievable to get winning results, but would be interested in hearing some discussion of the relative importance of some of these types of factors. or is it mostly a case of playing decent cards, in position, with acceptable pot odds?

Twisted Sister on 7/18/2008 11:46 AM (600 days ago)

Creating and USING a bot should be done entirely at your own risk. You WILL get caught if you bot and you WILL get banned. It's not really worth the risk. Learn programming skills like this and put it to use for other things, rather than destroying online poker and spoiling the game.

Poker League on 7/18/2008 12:33 PM (600 days ago)

H4mm3rhead:

I'm making a wild guess, sounds like your string is terminated by 00 after the first character. So my guess is the text is transmitted as unicode, "abcd" = (65 00 66 00 67 00 68 00) where as the same ansi string is 65 66 67 68.

\00 Null terminated string.

Take this with a big pinch of salt, I'm an amateur mixing/using way to many languages. Perhaps this is not the case in C#

BTW: thanks for that struct layout.

Nyx on 7/18/2008 12:53 PM (600 days ago)


"Learn programming skills like this and put it to use for other things"

sounds good to me

League of Independent Screen Scrapers and Hand History Parsers on 7/18/2008 2:07 PM (600 days ago)

It should be done entirely at your own risk.. always.. as with anything in life.. but you won't get banned.. you might get a nasty-gram from support.. but if you change the names of the executables.. probably not.. they are only able to identify well-known bots.. which possible the CTW exercises are well-known... who knows.. but in any case.. this bot doesn't violate anything, since it doesn't actually click anything!!

Poker League wrote:

>Creating and USING a bot should be done entirely at your own risk. You WILL get caught if you bot and you WILL get banned. It's not really worth the risk. Learn programming skills like this and put it to use for other things, rather than destroying online poker and spoiling the game.

Puh-leez

JeremyX on 7/18/2008 4:03 PM (600 days ago)

For those meddling with the code... I've had a load of stack corruption errors (runtime error #2)
Solution: in gametext.h, increase the size of the text array from 255, to atleast 511 bytes.

Nyx on 7/18/2008 5:42 PM (600 days ago)

"For those meddling with the code... "

yes. that would be most of us. as many variations as possible.

Anonymous on 7/18/2008 5:51 PM (600 days ago)



==== Go forth, mutate, search and replace, rename, evolve ====

Charlie Darwin on 7/18/2008 6:03 PM (600 days ago)

Awesome - thanks for posting the monitor bot source code. Very helpful to us fellow poker bot makers Smile


http://www.icmbot.com

Poker Bots on 7/18/2008 10:47 PM (600 days ago)

We will certainly hear more from James about stealth, but it is not too early for us to take basic steps.

It seems that one reasonable precaution is to rename files to more innocuous names than "MonitorBot", "FullTiltPokerClient", "StarsPokerClient", etc. and to do what we can to change file sizes, directory names, icons, etc. You will need to do this carefully before building.

(This is in addition to not botting 24/7)

Barney Rubble on 7/19/2008 1:59 AM (599 days ago)

@Nyx

Great that explains a lot, any idea on how to get past that? I could go and change in the C++ files (the original struct) but which datatype should i change it to?

Anonymous on 7/19/2008 4:41 AM (599 days ago)

@JeremyX on 7/18/2008 8:02:46 AM (22 hours ago)
(((
@Gamer - in order to extract the text, you have to put your code in the poker client's process. One way to do that is with CBT Hooks; CBT hooks are *also* useful for detecting when a window is created/destroyed. But you could inject the DLL and do the Detour text extraction without the CBT hook if you wanted.
)))
Ok, that is what I thought...Without the CBTHook would it be more difficult to distinguish which table the chat belongs to? Or is this all just done by parent name?

I'm trying to port this to an existing AutoIt hopper, the only thing I really need is the transcript data written to a separate file for each table.

The solution compiled with no errors on the first try with VS2005 btw, that was sweet! Now I'm trying to strip down to barebones minimum dll that just writes to file, and will probably inject using AutoIt...If anyone can offer some advice in this area it would be most appreciated...


Gamer on 7/19/2008 6:23 AM (599 days ago)

@Nyx you were right, every other character was a 00 so my app thought it ended there. Made the following tolution if anyone else have the same problem:

Fixed the C# part by creating a struct looking like this:


public struct GAME_TEXT
{
public int Venue;
public IntPtr Window;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=255)]
public char[] Text;
}

and to translate it to a string I made a simple method to help out:

private string ConvertCharArray(char[] input)
{
StringBuilder builder = new StringBuilder();
foreach (char letter in input)
{
if (letter != '\0')
{
builder.Append(letter);
}
}
return builder.ToString();
}


Anonymous on 7/19/2008 6:28 AM (599 days ago)

Cheers for the struct definition. Saved me a lot of work Smile

I've semi-succesfully ported the gui to C# now, the problem being when displaying the dealer chat I am getting garbled text at the start of each line:
Àè¤ê54³Ôw0–Dealer: Betting is capped

anyone experienced that?
Could be worked around by only displaying text after the work 'Dealer' - but i dont like that method.

Anonymous on 7/19/2008 4:53 PM (599 days ago)

Latest version of PokerStar appears not to include the hole cards in the log file anymore.

Anonymous on 7/19/2008 5:41 PM (599 days ago)

Anonymous: I am still seeing the hole cards... was there an update? Anybody else having this problem? If Poker Stars is making software changes based on this blog then... lol. Talk about picking your battles... and where's our author? Sanity check: make sure you're actually logged in and seated at a table....

JeremyX on 7/19/2008 6:52 PM (599 days ago)

Still got hole cards in the log.

The garbled text is most likely the pointer pointing (gosh) to the wrong address, in this case to an address 16 bytes before the address of the actual text. Which leads me to believe your pointer has the address of the struct itself and not the text.

You might want to do something like this:
pointer += Marshal.SizeOf(typeof(GAMETEXT)) - 256

Nyx on 7/19/2008 11:54 PM (599 days ago)


------------------------------------------------------------------------------------------

You probably already know this, but MonitorBot can serve as the core of an effective data miner. It just needs a parser to extract the desired data on each player observed, and an output to a text file or database. You don't even need to login to use it. It can be totally anonymous if you run it on a PC that you never play poker from.

------------------------------------------------------------------------------------------

Eddie Haskell on 7/20/2008 12:09 AM (598 days ago)

@James

Thank you. I feel like the ape in the movie 2001 who touches the obelisk.

Ape on 7/20/2008 2:12 AM (598 days ago)

Great writing, great programming. I hope to build a winning "human bot". I will do the clicking.

Agent Provocateur on 7/20/2008 4:11 AM (598 days ago)

Has anyone been able to find what writes the player's names and chip counts? I would assume that they are being written as a text somewhere. Are there other system calls that should be monitored here beyond drawText, CreateFile, WriteFile, CreateProcss, and OpenProcess?

Andrew G. on 7/20/2008 11:43 AM (598 days ago)

Andrew:
One straightforward way is by reading the hand history file, which is updated to your hard drive after each hand. It shows Seat#, player name, and chip count. The chip count could be kept accurate during the hand, if desired, by using the info from the game chat text.

Bossa Nova on 7/20/2008 1:05 PM (598 days ago)

Thanks for the info Bossa! I forgot that you had to toggle saving the hand history in Stars. After watching one hand you should be able to deduce how much a player has in front of them. w00t.

Andrew G. on 7/20/2008 1:31 PM (598 days ago)

Yes. As you know, relative stack sizes and the ratio of blinds to stack size are important considerations in modern poker decision making.

The challenge is incorporating this information, along with position, pot odds, opponent tendencies, number of players, board texture, your own cards, etc. in the correct proportions, into an automated winning strategy.

I like the approach here by James Devlin. Teaching and giving excellent examples of the modules necessary to build a winning bot.

Also, the ability to post here anonymously helps people contribute their own ideas.

Bossa Nova on 7/20/2008 3:04 PM (598 days ago)

Very interesting.

Btw, is there a reason why XPokerBot.Hook.dll is mapped into the address space of any process? Why not just in that of the poker client?

B. Strider on 7/20/2008 8:30 PM (598 days ago)

you can hard code it to the poker client of your choice if you wish

Oscar Mayer on 7/20/2008 8:50 PM (598 days ago)

>Need help! I' m enamored of J.D.

Well thanks popo, I ah... have a special someone in my life already, but I appreciate the note. ;) Have you downloaded VS2008 yet?

>Btw, is there a reason why XPokerBot.Hook.dll is mapped into the address space of any process?

No, not really. We're still using the global CBT hook to detect window create/destroy and that injects system-wide but like Oscar said, hard-code it if you wish. Or remove the call to SetWindowsHookEx if you're detecting windows some other way (EnumWindows or something like that).

James Devlin on 7/21/2008 12:33 AM (597 days ago)

Hey! Where's the Boost??

I spent 2 wks installing it now you're going to tell me it's gone?? wtf ;)

Anonymous on 7/21/2008 2:41 AM (597 days ago)

There is an old saying that "Those who can, do. Those who can't, teach." but James Devlin is a very rare example of someone who can obviously do both. He writes beautiful, well-structured code that works, and explains and illustrates it well. In addition, he has created this environment where we are completely free to ask questions, ranging from the very basic to advanced.

I don't know where this will all lead us, but it is VERY interesting. He is living a dream, and we are all in it.

We who are about to bot, salute you.

Deal Me In on 7/21/2008 2:53 AM (597 days ago)

There's so many people wanting to know "who owns PokerStars?!" that your google search turned up exactly one RELEVANT hit in the first couple pages... Smile

Eric on 7/21/2008 6:11 AM (597 days ago)

Can someone post a full version of the C# translation? I was going to do this myself but it looks like it's already been done.

JeremyX on 7/21/2008 9:09 AM (597 days ago)

What does the "Unglue Windows" button do?

Anonymous on 7/21/2008 7:04 PM (597 days ago)

>What does the "Unglue Windows" button do?

Just a convenience feature.

When windows are glued, the (for example) function call window is scrolled downward to show each new line of text as it occurs. Makes it hard to scroll up and see previous lines of text as the location keeps jumping to the bottom. When Windows are unglued, new lines of text are appended but the window isn't scrolled.

James Devlin on 7/21/2008 11:32 PM (597 days ago)

Thank you for explaining that. Sounds good. I was afraid to try it, since I did not want to unglue my windows ;)

Anonymous on 7/22/2008 1:19 AM (596 days ago)

I like the suggestion that MonitorBot could be used as part of a data miner, without even logging into the site. I think that it could just write the actions to a text file, and very closely approximate the hand histor files generated by the site while dealt into a hand. Does anyone know if it could be modified to mine multiple (up to 10) tables with only 1 instance of MonitorBot running, or would I be forced to run it once for each table that is to be mined? I am a slow programmer, so I would like to have some idea of the feasibility before I start plowing through it.

Miner 49er on 7/22/2008 2:22 AM (596 days ago)

@JeremyX
I did some C# stuff, but is relying on the code provided in the monitorbot, i simpley just altered the code to send messages to my C# application instead, and then intercept those and do my own logic. I do not rely on any logic in the injected dll, all logic is done in my C# application. If you (or anyone else) is going down this path, i would be happy to share some code, just tell what you need.

H4mm3rHead on 7/22/2008 10:11 AM (596 days ago)

@Miner 49er
Everytime you open a new window you get an event, and everytime anyone at any of the tables you have opened is "talking" (the chat) you are getting a message. So yes! this would indeed be possible, and is even build into the current implementation as far as i can see.

The kind of datamining you are proposing is a "runtime" evaluation of your cards to give instant statistics on how good they are, right? otherwise if you dont need the instant statistics part, it would be better to traverse the log files and make decisions based on those (like AceHUD does)

H4mm3rHead on 7/22/2008 10:17 AM (596 days ago)

@H4mm3rHead
As a data miner, I would not need to evaluate the cards on multiple tables in real time. I would just write the actions to text files in the format of the hand history files, which are now only available when dealt into a hand. The HH files that I manufacture from the text control would later be evaluated by PokerTracker, Holdem Manager, or my own application to obtain player stats.

I am optimistic that one instance of a modified MonitorBot could capture text from multiple tables, rather than needing multiple instances of MonitorBot to capture text from multiple tables. The observed resource requirements of MonitorBot (using Windows Task Manager) are very low.

The longer term goal is to incorporate villain characteristics of looseness and aggression into a bot's AI, while not ignoring the other indicators, such as pot odds, position, my own cards, etc.

Miner 49er on 7/22/2008 11:06 AM (596 days ago)

@DealMeIn, Nyx, Paul, BotEnvy, Urbane Plowboy, Ape, Agent Provacateur, Anonymous (did I miss anyone?): thanks for the compliments! Too kind. But I reserve the right to produce ugly, bad, evil code and second-rate writing at the drop of a hat.

@Eric: soon to be 2 I hope ;)

@H4mm3rHead: I see you noticed that the C++ code was a little messy, and had no business packaging that stuff into human-readable strings or doing anything but sending the raw data over to the C# app. At the time of posting I could barely hold my eyes open and P/Invoke is a bleak proposition at 3 in the morning, or whatever god-forsaken time of the day it was.

@Eddie Haskell, Miner49er: Yes this code could be turned into a data miner. Slap a few safeguards on it, extract a couple more pieces of information, and you've got everything you need to generate a complete hand history.

@H4mm3rHead: And the only thing about perusing the hand history files (rather than data mining the UI) is that some venues don't generate hand history files unless you're actually seated at the table. Some do. And some used to, but don't anymore.

@Anybody I missed: sorry, I need to get hierarchical comments going here. In the next week or two.

James Devlin on 7/22/2008 11:16 AM (596 days ago)

Author wrote: "At the time of posting I could barely hold my eyes open and P/Invoke is a bleak proposition at 3 in the morning, or whatever god-forsaken time of the day it was."

Yeah, that god-forsaken time of day known as: NOON. ;)

(assuming you were in the US of A)

Anonymouse on 7/22/2008 11:43 AM (596 days ago)

@H4mm3rHead
Did you figure out
public double[] Limits = new double[2];

BTW the Convert char to string function worked great.

Thanks

LastChance on 7/22/2008 7:31 PM (596 days ago)

Ok with the
TransmitText function why is this called again for FTP when the window is brought up.
Example I have the coding wheel app up. I then click over to the ftp game window it seems to send the last 2 lines of chat.

LastChance on 7/22/2008 8:24 PM (596 days ago)

Also noticing that transmittext does not get called if the FTP game window has been minamized.

LastChance on 7/23/2008 12:35 AM (595 days ago)

@LastChance
I didnt do that one, i only monitor the open and close of windows, and then the chat, i made my own limits decoding function in my C# function. The only thing i do is to detect open and close of window (new table opened) and then i send all the chat to my application. Thats it! then my C# application is in charge of forwarding the chat to the proper table instance in my code to actually do the deciphering. This is much easier for me, since im not that familiar with C++. In regards to your limits problem, i would recon that its the same deal as with the char/string routine you mentionen, just converting it to a double afterwards (so instead of charToString you do char to double)

H4mm3rHead on 7/23/2008 5:49 AM (595 days ago)

In regards to multiple tables and forwarding the details back to your C# program for filtering..
How does it work in stars with the log file? I havent quite got to that part yet, is there a seperate file for each table? or does it spam it all into 1? in which case is there a good way of differentiating between tables?

JB on 7/23/2008 5:29 PM (595 days ago)

JB, Poker Stars spams everything into the current log file but it emits an 8-character HWND in hexadecimal format indicating the "current" table. You can see these scattered throughout the log.

James Devlin on 7/23/2008 9:26 PM (595 days ago)

James maybe you can answer my limit question.
Its not the same as the string one unfortinoatyly because running the program as soon as it trys to cast the struct it gets a error. If I remove the double then there is no issue. Also would you advise doing the button click the same way that you did with pokertime?

Anonymous on 7/23/2008 11:25 PM (595 days ago)

Can you tell me where have i put a peace of code in order to save the chat text into a seperate file. The Classname in which i can do this in the xPokerbot sources.

Anonymous on 7/25/2008 7:13 AM (593 days ago)

"tell me where have i put a peace of code in order to save the chat text into a seperate file"

I don't know how to do this either, but there should be many examples of writing text files for Vis C++.

Anonymous on 7/26/2008 10:43 PM (592 days ago)

H4mm3rHead

Have you been able to convert this function to C#?

PerformAction(HWND hPokerTable)

LastChance on 7/27/2008 5:56 PM (591 days ago)

I do not advise you to download executables from unknown sources. You are safer downloading code and building it yourself.

Anonymous on 7/28/2008 12:12 AM (590 days ago)

*************************************************************************************************
Beware of PoisonBots that are able to send info on your account name, password, hole cards, and/or current table to a remote location. Download source code only. Look at the code and understand it before you compile and use it. Ask questions.
*************************************************************************************************

Anonymous on 7/28/2008 2:26 PM (590 days ago)

>Beware of PoisonBots that are able to send info on your account name, password, hole cards, and/or current table to a remote location.

This is good advice. Never download a botting executable (or any executable) from an untrusted source. Coding the Wheel sample downloads are SOURCE CODE ONLY for this reason. If someone posts a link to a "poker bot" please investigate it before downloading and using it.

James Devlin on 7/28/2008 3:10 PM (590 days ago)

Hello all I just built the MonitorBot and the file monitor thingy. I'm seeing some of the poker clients open up some strange files. Stuff in my browser history and so forth, registry entries, and etc. Anybody else seeing the clients opening files they shouldn't be?

Anonymous on 7/29/2008 7:12 AM (589 days ago)

no problems for me

Anonymous Too on 7/29/2008 10:50 AM (589 days ago)

@LastChance
Yeah i did that one too, i roughly translated the code into my C# application and created this method:


private void PerformButton1Click()
{
Win32.RECT myRect = new Win32.RECT();
Win32.GetWindowRect((IntPtr)table.WindowHandle, out myRect);

float button1XOffset = myRect.Width * 0.34f;
float button1YOffset = myRect.Height * 0.07f;
Point oldPoint = new Point(myRect.Right - (int)button1XOffset, myRect.Bottom - (int)button1YOffset);
Cursor.Position = oldPoint;
Thread.Sleep(200);
Win32.DoMouseClick(oldPoint);
}

I have divided the buttons on the front into 3, button1, button2 and button3 (hence the names). Since the buttons are scaled with the window i have a factor that denotes the distance from the right side or the bottom of the window to the acctual button (the 0.34 * window width is the location of the first button [fold button]). Then i just move the mouse there and simulate a mouse click - works fine Smile

BTW here are the methods and structs:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;

public int Width
{
get { return Right - Left; }
}

public int Height
{
get { return Bottom - Top; }
}
}
...

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);

private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;
//private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
//private const int MOUSEEVENTF_RIGHTUP = 0x10;


public static void DoMouseClick(Point point)
{
//Call the imported function with the cursor's current position
int X = point.X;
int Y = point.Y;
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, X, Y, 0, 0);
}

Hope you get it working, i just got mine up and folding to every bet.. i still struggle though , with how to determine which actions are available to me when its my turn...

H4mm3rHead on 7/29/2008 11:07 AM (589 days ago)

TY H4mm3rHead
I think i got something for it btw if you got a way for me to contact you other tahn here let me know.

LastChance on 7/29/2008 11:58 PM (589 days ago)

H4mm3rHead

Still don't have this click working right. Is there a reason that you are using thread.sleep?
Also the .34 how did you come across that number?


I was trying this to go through ranges but only seems to work if my curor is actually over the fold button

Single s = 0.34f;
Single x = .32f;
while (x < .35f) {
s = (0.34f + x);
x = x + .01f;
float button1XOffset = myRect.Width * s;
float button1YOffset = myRect.Height * 0.07f;
Point oldPoint = new Point(myRect.Right - (int)button1XOffset, myRect.Bottom - (int)button1YOffset);
//Cursor.Position = oldPoint;
//Thread.Sleep(200);
Debug.WriteLine(x.ToString());
DoMouseClick(oldPoint);
}

LastChance on 7/30/2008 12:59 AM (588 days ago)

I think this might actually be missing the mouse move before doing the mouse click. Let me know.

LastChance on 7/30/2008 1:25 PM (588 days ago)

@LastChance
The sleep is just to let me see when i click, you can remove it if you want to. The .34 is the ratio to multiply to the windows width. Because the buttons get resized when you resize the window (tiling eg. 4 tables) you eed to get the loctions of the buttons. I simpley just measured how far from the right side the fold button was and then divided by the width of the window and came up with that ratio. You should do a GetWindowRect call to get the location of the table window, then multiply the width with the ratio and subtract it from the right edge.. this should work...

like in my previous example:

float button1XOffset = myRect.Width * 0.34f;
float button1YOffset = myRect.Height * 0.07f;
Point oldPoint = new Point(myRect.Right - (int)button1XOffset, myRect.Bottom - (int)button1YOffset);

H4mm3rHead on 7/31/2008 1:00 PM (587 days ago)

Update: This is the best fun ever, got mine working perfectly, gotta do some serious AI though, its not so good at winning Smile


Btw: a cool library for C# poker statistics: http://www.codeproject.com/KB/game/pokerhandevaldoc.aspx

Update: To get the options available to me each tim e its my turn, i modified the xpokerbot dll to not only look at the FTCChat window but also the skinbutton and send me those messages. then its simply just a matter of seeing what they send to me...

H4mm3rHead on 7/31/2008 1:27 PM (587 days ago)

So I moved to 64 bit today and now i don't seem to be betting the call backs from the app if anyone has any idease let me know.

LastChance on 8/1/2008 12:27 AM (586 days ago)

List of Computer Poker Literature
www.pokerai.org/.../Computer_poker_literature

Anonymous on 8/2/2008 2:51 PM (585 days ago)

Perhaps we are missing something, but it seems that Poker Stars has disabled the extended dealer options?

Is anyone else encountering this? It's quite possible that we have misunderstood, but it all compiles and seems to be missing the significant table information (namely the cards). I assume this is because the extended dealer info isn't turned on. Did this get moved somewhere else? Or removed?

Most importantly: how to make the information appear so that we can extract it via the functions?

Him on 8/3/2008 10:55 PM (584 days ago)

Poker Stars Hole cards go to the log file
FTP you will see hole cards in the chat window.

LastChance on 8/4/2008 9:57 AM (583 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 AM (582 days ago)

@LastChance
Well... i first reacted on the "you have 15 seconds to act" text, but that was too obvious that i was a bot always waiting for that text. I made a temp solution (maybe ill stick with it) i make two things:

* I altered the C++ to also send me all the button activation events (the activated function that informs you that a window have been opened, modify it to also listen for the TC...SkinButtton and transmit that also. This gives you which actions are available to you.

* I made a timer to check every 3 seconds if a special color is present at a special point on the board Smile the fold button will always be active (showing either fold/check). And i simply just check for a blueish color at the point i click it, if i find the color, its my turn Smile

I ran into a different problm though, was trying to do this on another Venue (Ladbrokes) they also transmit the hole cards into chat (and it seems that they have a top level window as chat) But unfortunately i get a lot of exceptions, and i think its related to the fact that thwe window handle is invalid for some reason... it shows only 6 digits not 8 as it is supposed to. Spy++ says its invalid, and the code makes a "memory around variable 'c' is corrupted" in OnlinePokerClient.cpp line 63. Anyone have an idea of what is wrong?

H4mm3rHead on 8/5/2008 11:19 AM (582 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/6/2008 11:46 AM (581 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/6/2008 3:36 PM (581 days ago)

@LastChance
Why bother trying to control which turn it is.. it will get problematic, when people are sitting out and when people are joining...etc. My method for determining whether or not its my own turn works great:

//looks to see if the button is blue
public void CheckForButtonAndMakeAction()
{
try
{
Win32.RECT myRect = new Win32.RECT();
Win32.GetWindowRect((IntPtr)WindowHandle, out myRect);

//always a button at space number 1
float button1XOffset = myRect.Width * 0.34f;
float button1YOffset = myRect.Height * 0.07f;
Point oldPoint = new Point(myRect.Right - (int)button1XOffset, myRect.Bottom - (int)button1YOffset);

Bitmap bmpScreenshot = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(oldPoint.X, oldPoint.Y, 0, 0, new Size(1, 1), CopyPixelOperation.SourceCopy);
Color myColor = bmpScreenshot.GetPixel(0, 0);
//Console.WriteLine(string.Format("R:{0} G:{1} B:{2}", myColor.R.ToString(), myColor.G.ToString(), myColor.B.ToString()));
if (myColor.B > myColor.R && myColor.B > myColor.G)
{
MakeDecision();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

H4mm3rHead on 8/8/2008 11:05 AM (579 days ago)

Are you just calling CheckForButtonAndMakeAction on a endlessloop then?
I will give it a try
are the offsets that you post for PS or FTP
If you have the ones for FTP I would apprecaite them.

Thanks

LastChance on 8/8/2008 9:35 PM (579 days ago)

Tried to hook the example to Poker Academy but all what I get is the text of the main window. The file monitors seem to work OK. Only DrawTextEx seems to produce output. Spy++ does not find any other windows in the PA UI than com.biotools.poker.PokerApp.

WTF, maybe that PA is a java app has something to do with this ??. Any ideas ??

tatsa on 8/10/2008 2:11 PM (577 days ago)

What are reasonable methods online poker rooms might use to detect these 'bots'?
- scanning process names
- scanning window names

For those of us new to Windows development, how do we do simple things like change these simple values to protect ourselves?

What about the 'fingerprint' of the executable?

Obfuscation on 8/10/2008 5:13 PM (577 days ago)

Obfuscation:

- Many of these poker clients inject global hooks to monitor the keyboard and mouse to see if they are controlled by a program.
- They might also go through your internet surf history to find out if you have been here Smile.
- Registry scanning for unwanted programs.
- Taking screen grabs to see what you are doing.

There is software to defend against hooking (ProcessGuard).Nevertheless by using it you reveal the fact that you don't want your computer to be snooped.

For all fellow botters:

- Don't underestimate the capability of the venue to snoop your computer.

tatsa on 8/11/2008 12:49 AM (576 days ago)

I know that a popular MMORPG client did indeed do some of these very things. It was seen going through window names and process lists and looking for specific signatures, but the mmorpg claimed they only hashed the window and process titles and unless they matched a very small list of things they were looking for, they couldn't know what you were doing.

There was suspicion that they did screen grabs bc one hack visually hid itself as an option, but there was never any confirmation. Running processguard may in the worst case get your account flagged by the mmo, but even as aggressive as they were, it never got you banned. False positives were terrible for PR.

I get the sense that online casinos have even less to lose because of mediocre bots and as the author points out, gains quite a bit. So I doubt they would engage in such aggressive measures and risk the bad PR. In fact, I'm confident screengrabs and collecting actual window titles would be grounds for legal action against whoever was doing the snooping. However, I'm sure a few of us checking with something like ProcessGuard would give be helpful.

Having said that, a bit of search and replace allowed me to change the window title. IF the claim that they are only hashing windows is true, then simply adding a random character would be enough to throw the hash. I went a step further and changed to to be something completely different. If they were actually taking screen grabs, there's little you can do about that.

As for the process and executable name, it was a little less obvious, but still worth kicking myself for. I just needed to right click on the project name in the explorer on the left and rename it. The source files can retain the names and I don't think it matters. Of course as I write this, I wonder if I could have simply changed the .exe name ><

Obfuscation on 8/11/2008 8:57 AM (576 days ago)

Oh and hooking the keyboard and mouse would be definitely grounds for legal action, assorted troubles because it exposes them to private info like cc numbers and passwords.

Obfuscation on 8/11/2008 8:58 AM (576 days ago)

Being less savvy with windows than I am with *nix, I can't confirm this on a more basic level than using the Windows Task Manager and looking at the process list (the equivalent on a mac or linux box is to look at top). With regard to changing the process name in this domain, simply renaming the file does seem to do the trick. E.g. renaming XPokerBot.MfcView.exe to calculator.exe will indeed display calculator.exe in the process list.

Questions:
- can the source directory for this process be detected? E.g. if my 'calculator.exe' resides in a directory called 'omgimabot', will they know that?

Obfuscation on 8/11/2008 11:06 AM (576 days ago)

Obfuscation: This is very obvious for most I'd think, but to make it absolutely clear:

Any software running on you computer, can potentially do anything with your computer.
So yes, "they" can snoop the directory, or even download your calculator.exe

As for any legal action; which country is the venues servers running? Maybe in a country where no such laws exist. But most probably you've already accepted this snooping when you clicked "I accept" on the terms of use/lisence agreement.

Nyx on 8/11/2008 11:29 AM (576 days ago)

Nyx:

Potentially, yes. But there's a fine line between what someone who fears no consequences (e.g. virus author) can do versus a company with a name like Pokerstars or Fulltilt who has to deal with the public.

If one of the clients is outright downloading window title strings and grabbing screens or browser history wholesale, they would face quite the backlash from the community. The closest thing they've gotten away with is comparing hashed results, which protects the end users' privacy, but allows the client program to check only for _specific_ things. This is why detecting known hacks and bots is trivial.

In the MMORPG I was referring to, successful hacks even to this day get away with it simply by adding a random string to their window title - thus screwing the hash. For those unfamiliar with this tactic, here's a summary. If, for example, they're looking for "XPokerBot", one approach which allows them to not invade privacy is to hash that string "XPokerBot" into a unique identifier like 'ABCDEFG' (to overly simplify things). Then, if they rifle through your window titles, they use the same technique of hashing all window titles, each generating an identifier. Here's an example:

- "How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt - Coding the Wheel" might be hashed into 'QWERTYY'
- "Windows Task Manager" might be hashed into 'LKJHGFD'
- if you were running a window with the title "XPokerBot", it would also generate the hash 'ABCDEFG'

These hashes are then used for comparison. The window with the hash 'ABCDEFG' would match and alert whoever needed to know. Of course, they would double check with other methods or flag your account for further scrutiny.

If you were able to rename your window title to "XPokerBot2", the hash would be completely different. It may work out to be 'ABCDEFH' or 'ZCZCZCZC', but the point is, it won't match and they can make no assumptions about it.

In reality, they would probably be checking for a number of possible hashes, but these mmo hacks which are still successful defeat this method of detection simply by adding a randomly generated string or completely replacing the window title with a randomly generated string or something you can specify yourself.

I hope that makes sense.

Obfuscation on 8/11/2008 12:22 PM (576 days ago)

It makes perfect sense.

But I doubt they simply hash the windowtitle, it's just as easy to hash dll's or other executables memory image. And if said piece of software resides within their softwares memoryspace, like the hook.dll, they are probably within the legal limits to do just that.

Why hash in the first place? I can think of 2 reasons
1. It saves bandwidth at the expense of clientside cpu cycles.
2. It's close to impossible to detect whats going on with a simple network analyzer/sniffer.

How to defeat it?
Randomization, as obfuscation points out, and encryption.
What I'm really curious about, what is it James has up his sleeve (pocket aces? Laughing )

Paranoia is healty Smile
Nyx

Nyx on 8/11/2008 3:04 PM (576 days ago)

Why hash in the first place? I can think of 2 reasons
1. It saves bandwidth at the expense of clientside cpu cycles.
2. It's close to impossible to detect whats going on with a simple network analyzer/sniffer.


It also gives them legal shelter should anyone scream invasion of privacy. Hashing means they won't know anything except whether or not a hash matched one of their known hashes. If they didn't hash, it would be a legal field day.

Paranoia is quite healthy indeed.

Obfuscation on 8/11/2008 3:42 PM (576 days ago)

Hello,
Can someone please help me with integrating this project with C#?
I made a C# project inside of the solution, and changed the s_TargetExeCaption field in ApplicationProxy.cpp of XPokerBot.Hook to the caption of my project, and gave my C# program WM_COPYDATA handling. But since I don't even know if it's possible to use detours in C#, I added system(path to my C# exe) after the code that injects the dll when the Launch button is pressed in XPokerBot.MfcView.
Now when I run MfcView it injects PokerStars then runs my C# exe, but when I try to read the data sent from a WM_COPYDATA message from it's pointer, I get an error that says "Attempt to access protected memory". I'm guessing that this is because my C# program is not related to my injected pokerstars.exe, and the C++ exe doesn't have this problem because it is the parent of the pokerstars.exe.

Has anyone encountered this problem / does anyone have any other ways to integrate C# with the dll?

Anonymous on 8/11/2008 5:01 PM (576 days ago)

Take a look at part 6, The sources there has all you need

Nyx on 8/11/2008 7:38 PM (576 days ago)

Oh my god I just wasted so much time on something that was so simple and one page away. Thanks Nyx, people like you keep me from going crazy overthinking everything. Smile

Anonymous on 8/12/2008 3:25 AM (575 days ago)

Hello again,
I have tried to combine the code from part 6 into this part, which left me with a button in my C# program installing the hook. I have limited the injected dll to only send the "new table opened" message by commenting out the other functions. And I now have this for my WM_COPYDATA receiving code:

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
COPYDATASTRUCT cds = (COPYDATASTRUCT)
Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));

TABLE_SUMMARY ht = (TABLE_SUMMARY)Marshal.PtrToStructure(cds.lpData, typeof(TABLE_SUMMARY));

idBox.Text = ht.Window.ToString();
dataBox.Text = ht.TableName.ToString();
}
else
base.WndProc(ref m);
}


Here are the structs it refrences:
[quote]
const int WM_COPYDATA = 0x004A;

public struct COPYDATASTRUCT
{
public int dwData;
public int cbData;
public IntPtr lpData;
};

public struct TABLE_SUMMARY
{
public IntPtr Window;
public string TableName;
public string PlayerName;
public int LimitType;
public bool IsPlayMoney;
public bool IsTourney;
public double[] Limits;
public double[] Blinds;
public double Ante;
public int Venue;
public int SeatCount;
public int DisplayType;
}
[/QUOTE]

Before I implemented TABLE_SUMMARY into my program, my textboxes would show the pointer to the information fine. But now that I have TABLE_SUMMARY in, when it gets to that part of the code (I know that this happens at those lines since it only happens when I open a table and click on my app and back on the table), my program crashes. Even when I debug the program I get no information other than "APPCRASH".

Anonymouss on 8/12/2008 6:03 AM (575 days ago)

The problem is probably the double[] arrays, which has no defined size.

Nyx on 8/12/2008 9:45 AM (575 days ago)

You're right, when I comment them out the code runs. How exactly am i supposed to define their size? It won't allow me to use fixed or new double[2] in the struct

Anonymouss on 8/13/2008 3:57 AM (574 days ago)

I'm not certain exactly how, many options
I'm not to familliar with the interop, but something like this

[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
Public double[] Limits

or if you know the array is only 2 doubles wide, do a workaround like this
public double Limits1;
public double Limits2;

or the bad way (which I like) dont bother with the struct, use unsafe and manipulate the pointer

Nyx on 8/13/2008 6:19 AM (574 days ago)

Thank you very much Nyx. I used the MarshalAs method and now my C# application has all of the functionality that MfcView does. All I need to do now is to incorporate some logic into it and give it to the ability to click.

Anonymouss on 8/13/2008 5:35 PM (574 days ago)

James, with the code outlined in the articles there are various means to monitor table state. Given the hand history you can rebuild the table state since the end of the previous hand. With the chat window information you can monitor the state of the hand as it occurs. But the one part of the modeling that I cannot seem to update is the player's chip count. If a player buys in for more chips between hands it is not updated anywhere but the poker table window. How do you go about monitoring this in regards to table state? Do you hook the calls that render the names and numbers within the table windows?

Andrew G. on 8/17/2008 11:26 PM (570 days ago)

>But the one part of the modeling that I cannot seem to update is the player's chip count.

Here are a few options:

- First, make sure that information isn't being emitted into a log file, or some other easy-to-access location.
- If the poker client uses typical text output APIs, you can hook them, and infer from the X,Y coordinates which seat is being written to.
- If the poker client uses bitmapped fonts or other bit-blitting to draw the stack sizes, think about what it takes to generate an image of a numeral such as "4" and display it on the screen (without using DrawText/TextOut/etc.). Typically, it means loading an image, a bitmap font, or calling a traditional text output API on an in-memory bitmap, and subsequently blitting from that to the display surface. So the tiny little images for the "0" and the "1" etc. have to come from somewhere, by tracing backwards you can ferret this out.
- Beneath the UI, rest assured there's a data structure containing the stack sizes, and rest assured there's a function which gets called when a player's stack size changes. We'll talk more about this down the road.
- In certain clients, a window message will be directed to the table window when a stack size changes...
- Not to mention using OCR. That can mean a full-fledged 3rd-party OCR, although honestly, hand-rolled OCR is feasible if all you have to recognize are 0-9 and the decimal point. You can use tricks in this case similar to the three-pixel Ace of Spades test I mentioned back in Part 1.

It really depends on the specific client you're dealing with.

James Devlin on 8/18/2008 8:04 AM (569 days ago)

Thanks for the response James. I guess I should have mentioned it my previous post, but the client I'm having trouble with is Stars. I figure that the numbers displayed in the windows are written with some text output API, but what other text APIs are there other then the ones outlined in the code, TextOut, ExtTextOut, DrawText, DrawTextEx?

I like the idea of querying the underlying data structures. How difficult is that to implement? Hopefully in another post..

Andrew G. on 8/18/2008 6:54 PM (569 days ago)

How do we prevent ourselves from being detected when applications like the one below can view our injected .DLL?

http://www.nirsoft.net/utils/injected_dll.html

Mark on 8/19/2008 9:39 PM (568 days ago)

I use to be loosely associated with a poker stat collecting software app (not here to advertise though). From extensive readings in that forum and others I can tell you that only a few poker-sites state adamantly that bots and stat collection software that use centralized databases are not allowed. Of those... only two (PS & PP) actually use their client app to check your system.

They don't disallow all DLL injections on your system... too many of those are required by the system or major apps or for good purposes. They check for specific known products by tell-tell signs of it on your system (registry, file system, IE history). Products that have sold thousands of copies.

They aren't worried about the little guy building a bot for free that could be under any name today and maybe a different name next week... that may or may not be able to get his bot working, and may or may not be able to break even with it when he does. They're only worried about the big bot software companies whose technology could be used in collusion rings, or centralized stat databases where paying members get tons of stat data on players they've never played against or even seen. I know a bit about this... but I'm still here enjoying this fun project.

DM on 8/22/2008 12:11 AM (565 days ago)

Is it possible to grab text from a Java app? I'm trying to get this to work with Poker Academy, but the only function calls I see are CreateFile and WriteFile, no TextOut/ExtTextOut. How does the JVM put text in the window?

M on 9/10/2008 11:32 AM (546 days ago)

First of all thanks for your posts James. I have actually stopped playing poker to learn the bot code!

I am learning by messing around and extending your code. I have got to the point of getting the hole cards from the log file. And also the hexidecimal number for the PokerTable window ID.

My problem is that I now have a string type like 00020AC7 and I need to obtain the HWND handle to the poker table window that it corresponds to.

My programming skills are rather basic. I know there must be a simple way of doing it, but the "fight club" of bot programming is kicking my ass...

Any help / example code from anyone would be much appreciated.

Thanks

SteveT on 9/12/2008 2:05 PM (544 days ago)

Nice job

Poker Bot on 10/13/2008 4:49 AM (513 days ago)

@ H4mm3rHead or @ LastChance

I am way behind you guys but am trying to do what you have already done(go straight to C# then decode). I'm curious what code you added to the hook.cpp file (did you just write a transmit method and send the messages over in the detoured windows functions). I have a lot of other questions too, programming is only a hobby for me. If one of you would be so kind as to email me I would be very grateful.

billmatthews 0 at gmail.com (no spaces obviously)

bm on 11/4/2008 5:00 PM (491 days ago)

A bit shorter than the other article but still does a good job of describing how to build a monitor poker bot, thanks James.

Poker Celebrities on 11/5/2008 10:40 AM (490 days ago)

Looking forward to reading more about the AI side of things.

Poker Books on 11/6/2008 3:45 AM (489 days ago)

Question for the masses:

I have noticed that the window detection code doesn't work properly for Full Tilt (Application Proxy:addTable is never called). However, the table information still shows up in the function call window. Running in the debugger, InstallMonitors() is never called(the function that sets up the detours), but if I comment the InstallMonitors() function out, no information appears in the function call window. I guess this is a debugger error?

bm on 11/7/2008 1:20 PM (488 days ago)

Correction, add table is never called on a play money full tilt table. It turns out the decodeCaption class cannot correctly parse the blinds so it returns false. Probably has to do with the lack of $.

bm on 11/11/2008 10:11 AM (484 days ago)

hi james,

ive read ur articles with great pleasure! i am a professional c++ (and java) developer and the techniques you used aren't new to me, but your demo code allowed me to save some days of work! THANKS for that!

i am also a bad to average poker player and want to build a tool to record (and later guide) my play. i know pokertracker and pockeroffice, but i like my own tools more and of course its fun to do the programming by myself.


but i (like some other here) have a big problem: how can i determine the stack size of of a player at the BEGINNING of a hand (afterwards its easy done by reading the hand history). i need this information to make educated guesses about the desperation and therefore lowered hand range of my opponents. and after the bubble correct stack information is also needed to do a correct ICM calclulation. do you know something not stated in one of your articles?

greetings

bruno


bruno on 11/21/2008 5:39 PM (474 days ago)

Reviewing your code you often write: 'In production code, we'd probably want to use something more robust.' As below:

///////////////////////////////////////////////////////////////////////////////
// Inform the GUI that the user has opened a new table. For this sample, we're
// using WM_COPYDATA to handle inter-process communication (IPC). In production
// code, we'd probably want to use something more robust.
///////////////////////////////////////////////////////////////////////////////
void ApplicationProxy::AddTable(HWND hTable, TABLE_SUMMARY& state)
{
HWND hWnd = ::FindWindow(NULL, s_TargetExeCaption);
if (hWnd)
{
COPYDATASTRUCT cds;
::ZeroMemory(&cds, sizeof(COPYDATASTRUCT));
cds.dwData = WindowOpened;
cds.lpData = (PVOID)&state;
cds.cbData = sizeof(state);
::SendMessage(hWnd, WM_COPYDATA, (WPARAM)hTable, (LPARAM)&cds);
}
}

Forgive my ignorance, I am new to c++ and c# as I am more of a java programmer but wanting to move over, can you give us an example of a more robust piece of code or highlight what you would change, adding error checking etc. Just curious and so intrigued by all this.

Flava on 11/24/2008 5:50 PM (471 days ago)

pp

Anonymous on 11/28/2008 2:31 PM (467 days ago)

Hi,
i have a little problem !
i miss the "atlstr.h" from ATL.
only i have the C++ 2008 EE, the file is not included in this package.
what can I do ?

thanks

Tom on 11/28/2008 2:42 PM (467 days ago)

Here is some code for datamining Stars. The author donated the code to anyone who wants to use it. It is in C# and looks inside the pokerstars executable image to find names of players. It uses C# wrappers to MSC functions which allow programs to access the memory used by another process. You will need to be familiar with C# inorder to make any use of this.

The code below corrects player name spelling by finding the list of players in the pokerstars process which matches at least 1/3 of the names. The original list was created by scraping the tourney lobby and using Optical Character Recognition. The OCR code had problems with the non-English characters so I corrected them by looking in the PokerStars memory. I couldn't just look in the memory because the tournament number and the buyin/type/size info is not near the names (atleast I couldn't find it using ollydbg.)

StarTracker is on the approved list for PokerStars because we honored the rules PokerStars layed out for player databases. Specifically, we did not report bankroll amounts/ROI and we allowed players to block their stats.

Here is the class to read from PokerStars memory...

Code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Diagnostics;

namespace ProcessMemoryReaderLib
{
/// <summary>
/// ProcessMemoryReader is a class that enables direct reading a process memory
/// </summary>
class ProcessMemoryReaderApi
{
// constants information can be found in <winnt.h>
[Flags]
public enum ProcessAccessType
{
PROCESS_TERMINATE = (0x0001),
PROCESS_CREATE_THREAD = (0x0002),
PROCESS_SET_SESSIONID = (0x0004),
PROCESS_VM_OPERATION = (0x0008),
PROCESS_VM_READ = (0x0010),
PROCESS_VM_WRITE = (0x0020),
PROCESS_DUP_HANDLE = (0x0040),
PROCESS_CREATE_PROCESS = (0x0080),
PROCESS_SET_QUOTA = (0x0100),
PROCESS_SET_INFORMATION = (0x0200),
PROCESS_QUERY_INFORMATION = (0x0400),
PROCESS_ALL_ACCESS = (0x1F0FFF)
}

// function declarations are found in the MSDN and in <winbase.h>
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)] ref SYSTEM_INFO lpSystemInfo);

[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
internal _PROCESSOR_INFO_UNION uProcessorInfo;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
[StructLayout(LayoutKind.Explicit)]
public struct _PROCESSOR_INFO_UNION
{
[FieldOffset(0)]
internal uint dwOemId;
[FieldOffset(0)]
internal ushort wProcessorArchitecture;
[FieldOffset(2)]
internal ushort wReserved;
}

[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
internal uint BaseAddress;
internal uint AllocationBase;
internal uint AllocationProtect;
internal uint RegionSize;
internal uint State;
internal uint Protect;
internal uint Type;
}
[DllImport("kernel32.dll")]
public static extern uint VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress,
out MEMORY_BASIC_INFORMATION lpBuffer, UIntPtr dwLength);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
UIntPtr dwSize, uint flAllocationType, uint flProtect);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static unsafe extern bool VirtualFreeEx(
IntPtr hProcess, byte* pAddress,
UIntPtr size, AllocationType freeType);

[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000
}

// HANDLE OpenProcess(
// DWORD dwDesiredAccess, // access flag
// BOOL bInheritHandle, // handle inheritance option
// DWORD dwProcessId // process identifier
// );
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

// BOOL CloseHandle(
// HANDLE hObject // handle to object
// );
[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(IntPtr hObject);

// BOOL ReadProcessMemory(
// HANDLE hProcess, // handle to the process
// LPCVOID lpBaseAddress, // base of memory area
// LPVOID lpBuffer, // data buffer
// SIZE_T nSize, // number of bytes to read
// SIZE_T * lpNumberOfBytesRead // number of bytes read
// );
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesRead);

// BOOL WriteProcessMemory(
// HANDLE hProcess, // handle to process
// LPVOID lpBaseAddress, // base of memory area
// LPCVOID lpBuffer, // data buffer
// SIZE_T nSize, // count of bytes to write
// SIZE_T * lpNumberOfBytesWritten // count of bytes written
// );
[DllImport("kernel32.dll")]
public static extern Int32 WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesWritten);


}

class AdvAPI
{
public static bool MakeProcessDebuggable(IntPtr process)
{
IntPtr hToken;

TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
TOKEN_PRIVILEGES oldtp = new TOKEN_PRIVILEGES();
LUID luid;

if (!AdvAPI.OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
{
return false;
}

if (!LookupPrivilegeValue("", "SeDebugPrivilege", out luid))
{
ProcessMemoryReaderApi.CloseHandle(hToken);
return false;
}

tp.PrivilegeCount = 1;
tp.Luid = luid;
tp.Attributes = 0x2;

/* Adjust Token Privileges */
UInt32 dwSize;
if (!AdvAPI.AdjustTokenPrivileges(hToken, false, ref tp, 100, ref oldtp, out dwSize))
{
ProcessMemoryReaderApi.CloseHandle(hToken);
return false;
}
// close handles
ProcessMemoryReaderApi.CloseHandle(hToken);
return true;
}

//struct TOKEN_PRIVILEGES
//{
// public int PrivilegeCount;
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
// public LUID_AND_ATTRIBUTES[] Privileges;
//}
//[StructLayout(LayoutKind.Sequential)]
//struct LUID_AND_ATTRIBUTES
//{
// public LUID Luid;
// public int Attributes;
//}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public int PrivilegeCount;
public LUID Luid;
public int Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public uint HighPart;
}
[DllImport("advapi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AdjustTokenPrivileges(IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
UInt32 BufferLength,
ref TOKEN_PRIVILEGES PreviousState,
out UInt32 ReturnLength);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool LookupPrivilegeValue(string lpSystemName, string lpName,
out LUID lpLuid);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);
private static uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
private static uint STANDARD_RIGHTS_READ = 0x00020000;
private static uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private static uint TOKEN_DUPLICATE = 0x0002;
private static uint TOKEN_IMPERSONATE = 0x0004;
private static uint TOKEN_QUERY = 0x0008;
private static uint TOKEN_QUERY_SOURCE = 0x0010;
private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private static uint TOKEN_ADJUST_GROUPS = 0x0040;
private static uint TOKEN_ADJUST_DEFAULT = 0x0080;
private static uint TOKEN_ADJUST_SESSIONID = 0x0100;
private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
private static uint TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID);
}
public class ProcessMemoryReader
{

public ProcessMemoryReader()
{
}

/// <summary>
/// Process from which to read
/// </summary>
public Process ReadProcess
{
get
{
return m_ReadProcess;
}
set
{
m_ReadProcess = value;
}
}

private Process m_ReadProcess = null;

private IntPtr m_hProcess = IntPtr.Zero;

public void OpenProcess()
{
// m_hProcess = ProcessMemoryReaderApi.OpenProcess(ProcessMemoryReaderApi.PROCESS_VM_READ, 1, (uint)m_ReadProcess.Id);
ProcessMemoryReaderApi.ProcessAccessType access;
//access = ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_READ
// | ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_WRITE
// | ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_OPERATION;
access = ProcessMemoryReaderApi.ProcessAccessType.PROCESS_ALL_ACCESS;
AdvAPI.MakeProcessDebuggable(m_ReadProcess.Handle);
m_hProcess = ProcessMemoryReaderApi.OpenProcess((uint)access, 1, (uint)m_ReadProcess.Id);
}

public void CloseHandle()
{
int iRetValue;
iRetValue = ProcessMemoryReaderApi.CloseHandle(m_hProcess);
if (iRetValue == 0)
throw new Exception("CloseHandle failed");
}

public byte[] ReadProcessMemory(IntPtr MemoryAddress, uint bytesToRead, out int bytesRead)
{
byte[] buffer = new byte[bytesToRead];

IntPtr ptrBytesRead;
ProcessMemoryReaderApi.ReadProcessMemory(m_hProcess, MemoryAddress, buffer, bytesToRead, out ptrBytesRead);

bytesRead = ptrBytesRead.ToInt32();

return buffer;
}

public void WriteProcessMemory(IntPtr MemoryAddress, byte[] bytesToWrite, out int bytesWritten)
{
IntPtr ptrBytesWritten;
ProcessMemoryReaderApi.WriteProcessMemory(m_hProcess, MemoryAddress, bytesToWrite, (uint)bytesToWrite.Length, out ptrBytesWritten);

bytesWritten = ptrBytesWritten.ToInt32();
}
}
}

Here is the code which fixes a List of names and finish positions (fix_scanned_names) by looking into the actual PokerStars process. It basically finds an array of a structure in PokerStars memory which holds the name, location and finish position for each player in the tourney. This array is filled in when the tourney lobby is shown in PokerStars client.


Code:
static int BYTESBEFORENAME = 30;
static int BYTESPASTLOCATION = 23;
static ProcessMemoryReaderApi.MEMORY_BASIC_INFORMATION mbi = new ProcessMemoryReaderApi.MEMORY_BASIC_INFORMATION();
static unsafe byte* lpMem = null;

private bool get_name_from_mem(byte[] buffer, int buffsize, int t, out string name, out int pos)
{
name = "";
pos = -1;
if (buffer[t] != 0x00)
{
return false;
}
//byte[] temp_buffer = new byte[100]; int ik = 0;
//for (int ij = t; ij < t + BYTESPASTLOCATION + 30 + BYTESBEFORENAME; ij++)
//{
// temp_buffer[ik++] = buffer[ij];
//}
int i = t + BYTESBEFORENAME;

if ((i > buffsize) || (buffer[i - 5] != 0xFF))
{
return false;
}
StringBuilder sb2 = new StringBuilder();
while ((i < buffsize - 1) && (buffer[i] != 0x00))
{
sb2 = sb2.Append((char)buffer[i++]);
}
name = sb2.ToString();

// Skip over location
i++;
while ((i < buffsize - 1) && (buffer[i] != 0x00))
{
i++;
}
i++;
if (i >= buffsize) return false;
pos = (int)buffer[i + 3];

return true;
}
private bool next_player(byte[] buffptr, int buffsize, ref int t)
{
int i = t + BYTESBEFORENAME;

while ((i < buffsize) && (buffptr[i] != 0x00))
{
i++;
}
i++;
while ((i < buffsize) && (buffptr[i] != 0x00))
{
i++;
}
i++;
i += BYTESPASTLOCATION;

t = i;
return true;
}
private bool previous_player(byte[] buffptr, int buffsize, ref int t)
{
int previoust = t;
int i = t;

if ((i < 6) || (buffptr[i-6] != 0xFF)) return false;

i -= BYTESPASTLOCATION; // back up to \0 after location.

i--; // before \0
while ((i >= 0) && (buffptr[i] != 0x00))
{
i--;
}
i--; // before \0
while ((i >= 5) && (buffptr[i-5] != 0xFF))
{
i--;
}

i -= BYTESBEFORENAME;
if ((i < 0) || (buffptr[i] != 0x00)) return false;
t = i;

//byte[] temp_buffer = new byte[previoust-t+1]; int ik = 0;
//for (int ij = t; ij <= previoust; ij++)
//{
// temp_buffer[ik++] = buffptr[ij];
//}
return true;
}
private int find_start_name_range(string scanned, ref byte[] buffptr, ref int buffsize, ref int i)
{
char[] sbuff = scanned.Trim(new char[] { '_' }).ToCharArray();
int sbuffsize = sbuff.GetLength(0);
bool found = false;
int found_at = -1;

try
{
while ((i < buffsize) && (!found))
{
int j = 0;

int k = i;

while ((j < sbuffsize) && (k < buffsize) && (!found))
{
// Skip the wildcards.
//while ((j < sbuffsize) && (sbuff[j] == '_'))
//{
// j++;
//}
if (j < sbuffsize)
{
if (sbuff[j] == (char)buffptr[k])
{
j++;
k++;
if (j == sbuffsize)
{
found = true;
}
}
else
{
break;
}
}
}

if (found)
{
int namestart = i - BYTESBEFORENAME;
while (previous_player(buffptr, buffsize, ref namestart))
{
// System.Console.WriteLine(namestart);
}
i += 50;
found_at = namestart;
break;
}
else
{
i++;
}
}
}
catch (Exception ie)
{
MyWebMethods.MessageLog("Exception: " + ie.Message);
}
return found_at;
}
// Get names in memory location
private int get_memory_names(byte[] buffptr, int buffsize, int start_of_names, ref List<string> memory_names, ref List<int> position)
{
int pos = -1;
string name = "";
int last_start = start_of_names;

while (get_name_from_mem(buffptr, buffsize, last_start, out name, out pos))
{
memory_names.Add(name);
position.Add(pos);
next_player(buffptr, buffsize, ref last_start);
}
return memory_names.Count;
}

private unsafe bool fix_names_in_mbi( ProcessMemoryReaderApi.MEMORY_BASIC_INFORMATION mbi,
byte* lpMem,
ProcessMemoryReaderLib.ProcessMemoryReader pReader,
string scanned,
ref List<string> scanned_list,
ref List<int> finish_pos )
{
bool foundit = false;
System.DateTime dt = System.DateTime.Now;
int start_of_names = -1;

int buffsize = 0;
byte[] buffptr = pReader.ReadProcessMemory((IntPtr)(void*)lpMem, mbi.RegionSize, out buffsize);
int i = 0;
while ((i < buffsize) && (!foundit))
{
// If the name is found, save the buffer address so we don't
// have to look for the area again.
start_of_names = find_start_name_range(scanned, ref buffptr, ref buffsize, ref i);

if ((start_of_names >= 0))
{
// MyWebMethods.MessageLog("find_start end " + System.DateTime.Now.ToString());
List<string> memory_names = new List<string>();
List<int> mem_finish_pos = new List<int>();
// MyWebMethods.MessageLog("get_memory_names beg " + System.DateTime.Now.ToString());
int n_mem = get_memory_names(buffptr, buffsize, start_of_names, ref memory_names, ref mem_finish_pos);
// MyWebMethods.MessageLog("get_memeory_names end " + System.DateTime.Now.ToString());
if (n_mem == scanned_list.Count)
{
// See how many match.
int[] match = new int[scanned_list.Count];
for (int ii = 0; ii < scanned_list.Count; ii++)
{
match[ii] = memory_names.IndexOf(scanned_list[ii]);
}
// MyWebMethods.MessageLog("indexes found " + System.DateTime.Now.ToString());
int nmatch = 0;
for (int ii = 0; ii < scanned_list.Count; ii++)
{
if (match[ii] >= 0) nmatch++;
}

// MyWebMethods.MessageLog("matches = " + nmatch + "/" + scanned_list.Count);
if (nmatch == scanned_list.Count)
{
// All matched.
// System.Console.WriteLine("OK...mbi = " + mbi.Protect + " " + mbi.State.ToString() + " " + mbi.Type.ToString() + " " + mbi.BaseAddress + ":" + mbi.RegionSize);
foundit = true;
break;
}
else if ((float)nmatch / (float)scanned_list.Count > 0.33)
{
// We have the block of memory with some which don't match.
if (memory_names.Count == scanned_list.Count)
{
for (int ii = 0; ii < mem_finish_pos.Count; ii++)
{
if (scanned_list.IndexOf(memory_names[ii]) >= 0)
{
if (mem_finish_pos[ii] != (scanned_list.IndexOf(memory_names[ii]) + 1))
{
mem_finish_pos[ii] = (scanned_list.IndexOf(memory_names[ii]) + 1);
}
}
if (mem_finish_pos[ii] > mem_finish_pos.Count)
{
// MyWebMethods.MessageLog("Error: Bad position in memory.");
foundit = false;
break;
}
}
scanned_list.Clear();
finish_pos.Clear();
// MyWebMethods.MessageLog("fixed " + memory_names[0] + " : " + start_of_names.ToString());
for (int ii = 0; ii < memory_names.Count; ii++)
{
scanned_list.Add(memory_names[ii]);
finish_pos.Add(mem_finish_pos[ii]);
}

// This one is used.
// founded.Add(start_of_names);

// MyWebMethods.MessageLog("added " + System.DateTime.Now.ToString());
// System.Console.WriteLine("Fixed..mbi = " + mbi.Protect + " " + mbi.State.ToString() + " " + mbi.Type.ToString() + " " + mbi.BaseAddress + ":" + mbi.RegionSize);

foundit = true;
break;
}
else
{
// MyWebMethods.MessageLog("Number of players didn't match:" + memory_names.Count.ToString() + "(mem) != " + scanned_list.Count.ToString() + "(scan)");
}
}
else
{
// MyWebMethods.MessageLog("Not 33 percent in this: " + nmatch + " matched of " + scanned_list.Count.ToString());
}
}
else
{
// MyWebMethods.MessageLog("n_mem (" + n_mem + ") != scanned (" + scanned_list.Count.ToString() + ")");
}
}
else
{
// MyWebMethods.MessageLog("Not found..mbi = " + mbi.Protect + " " + mbi.State.ToString() + " " + mbi.Type.ToString());
}
}
return foundit;
}

// Correct scanned names list based on actual memory contents.
private bool fix_scanned_names(ref List<string> scanned_list, ref List<int> finish_pos )
{
bool foundit = false;

// Find a buffer with the name in it.
Process[] pArray = Process.GetProcessesByName("pokerstars");
if (pArray.Length == 0)
{
return false;
}

ProcessMemoryReaderApi.SYSTEM_INFO pSI = new ProcessMemoryReaderApi.SYSTEM_INFO();
ProcessMemoryReaderApi.GetSystemInfo(ref pSI);

// Create memory reader
ProcessMemoryReaderLib.ProcessMemoryReader pReader =
new ProcessMemoryReaderLib.ProcessMemoryReader();

// Take the first process found
pReader.ReadProcess = pArray[0];
pArray[0].PriorityClass = ProcessPriorityClass.High;

pReader.OpenProcess();
unsafe
{

// If we already have some
if (lpMem != null)
{
// MyWebMethods.MessageLog("n_look loop start " + System.DateTime.Now.ToString());
int nlook = 4; if (scanned_list.Count < nlook) nlook = scanned_list.Count;

for (int i = 0; i < nlook; i++)
{
if (fix_names_in_mbi(mbi, lpMem, pReader, scanned_list[i], ref scanned_list, ref finish_pos))
{
foundit = true;
break;
}
}
}
// MyWebMethods.MessageLog("n_look loop " + foundit.ToString() + " " + System.DateTime.Now.ToString());
if (foundit) return foundit;

int max_look = 5; int lookat = 0;

// couldn't find it in last mbi, look in all mbi's
// MyWebMethods.MessageLog("scanned loop " + System.DateTime.Now.ToString());

foreach (string scanned in scanned_list)
{
if (lookat >= max_look) break;
lookat++;

lpMem = null;

while (lpMem < (byte*)(void*)pSI.lpMaximumApplicationAddress)
{
ProcessMemoryReaderApi.VirtualQueryEx(pArray[0].Handle,
(IntPtr)(void*)lpMem,
out mbi,
(System.UIntPtr)sizeof(ProcessMemoryReaderApi.MEMORY_BASIC_INFORMATION));
if ((mbi.Protect == 4) && (mbi.State == 4096) && (mbi.Type == 131072))
{
if (fix_names_in_mbi(mbi, lpMem, pReader, scanned, ref scanned_list, ref finish_pos))
{
foundit = true;
break;
}
}

/* increment lpMem to next region of memory */
lpMem = (byte*)(void*)mbi.BaseAddress;
lpMem += mbi.RegionSize;
}
if (foundit) break;
}
// MyWebMethods.MessageLog("scanned loop end " + foundit.ToString() + " " + System.DateTime.Now.ToString());

}
return foundit;
}

Hopefully someone else can use the information here to write their own add-on for PokerStars.

Comments?

Not a Bot, but a data miner on 11/29/2008 9:14 PM (466 days ago)

data miner, how about a pastie version?

Anonymous on 11/30/2008 1:17 PM (465 days ago)



A New Pokerstars Client Update !

to disuse !

Tom on 12/12/2008 10:53 AM (453 days ago)

What is it that limits this software to Hold'em tables?

happygolucky on 12/18/2008 10:17 AM (447 days ago)

I tried the sample program today (Dec 23). The "game chat text" did not work for pokerstars. It works for Full Tilt Poker. Anyone knows why?

Anonymous on 12/23/2008 4:42 AM (442 days ago)

Pokerstars hat neue Clientsoftware seit 10.12.08
Darum funktioniert das nicht mehr.

Szefen on 12/23/2008 1:08 PM (442 days ago)

This is a great website with s much info.

I was thinking of writing my own poker bot, my know I do not know.

It seems like a lot of work.

large-shark on 1/15/2009 12:30 PM (419 days ago)

Could you please update the XPokerBot it doesn't work anymore with PokerStars it's like it wouldn't use any of the following functions:

case Venue_Stars:
DetourAttach(&(PVOID&)Real_TextOut, Mine_TextOut);
DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText);
DetourAttach(&(PVOID&)Real_DrawTextEx, Mine_DrawTextEx);
DetourAttach(&(PVOID&)Real_ExtTextOut, Mine_ExtTextOut);
break;

Anonymous on 1/16/2009 6:06 PM (418 days ago)

Hi I'm relatively new to C++. I try to build the simple hook in the detours sample directory in VS08, but I'm stuck. I built detoured.lib and detoured.lib (as empty general projects), but with simple.dll I get the following error:
error LNK2005: _DllMain@12 already defined in Simple.obj

I found this:
support.microsoft.com/default.aspx

but I still don't know what to change. I think all my preferences are the same as in XPokerBot, but I still get the error message.

Anyway, I had to define DETOURS_X86 in detours.cpp and detoured.cpp. How come that XPokerBot works without this?

keret on 1/27/2009 11:13 AM (407 days ago)

It seems that PokerStar just updated their client.
The DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText);
Works only on the menu screen and when I enter the table it is not working anymore.
Anyone got a clue???

Amir on 1/27/2009 6:20 PM (407 days ago)

Online poker bots seem so complicated to program, omg!

RakeBack Riches on 1/27/2009 9:10 PM (407 days ago)

Nice job!

We did something similar here:
www.pokerbot-smart.com

Poker Bot Software on 3/1/2009 12:04 PM (374 days ago)

Cool

Poker Bots on 3/9/2009 7:14 AM (366 days ago)

wow! this article incl. the comments is very cool! Such much informations in one blog-article is very impressive! Thanks so much - that was i was looking for!!

Surplus on 3/13/2009 5:51 PM (362 days ago)

@H4mm3rHead, LastChance

My solution to knowing when to act is to use detours to hook the "FlashWindowEx" function, which is called whenever it is your turn to act. Be sure to turn the "Bring window to front" option in the clients settings is turned off however.

Note also that I used this for PokerTime, I have not tested it on any other client but I see no reason why it wouldn't work (unless the client chooses not to call FlashWindowEx of course Tong).

Even though I use this method, it is more of an initial indication of table position (insurance if you like). I also build a virtual table state within my code, which I can then use when making a decision as to the best action to make.

gazzaaa87 on 4/7/2009 7:22 AM (337 days ago)

From the PokerStars binary:

004817e0 d __ZN11BotDetector12_botDetectorE
001c64e8 t __ZN11BotDetector13OnUserVCInputEPKc
001ca2b0 t __ZN11BotDetector13ProcessSignalEiPv
001c6570 t __ZN11BotDetector13ReportVersionEv
001c82ae t __ZN11BotDetector14OnHandFinishedEPKcjj
001c5cc8 t __ZN11BotDetector14postClientInfoEPKcttjttS1_
001c7088 t __ZN11BotDetector14postTablesInfoEijjjP6PBlockPKSt6vectorI10PStringExtSaIS3_EEPKS2_IjSaIjEE
001c5c1a t __ZN11BotDetector15GenerateVCImageEmPh
001c6020 t __ZN11BotDetector15OnButtonClickedEv
001c6a2a t __ZN11BotDetector15ParseRuleStringER10PStringExtS1_S1_RjS2_S2_
00201586 t __ZN11BotDetector17createBotDetectorEv
001c6d82 t __ZN11BotDetector17postResultToMonetEijjRK5_RectPKcjllm
001c61ea t __ZN11BotDetector17reportHandsPlayedEv
001c797e t __ZN11BotDetector18OnVisualConfirmMsgEjjR13MessageParser
001ca10c t __ZN11BotDetector18ProcessTimerSignalEP5Timer
001c8466 t __ZN11BotDetector19CheckMouseBehaviourEv
001c5bc4 t __ZN11BotDetector19ProcessDialogSignalEP6Dialogi
001c8d72 t __ZN11BotDetector21ConfigureMouseMonitorEv

Happy Botting on PokerStars everyone!

Orwellophile on 4/10/2009 12:57 AM (334 days ago)

Anyone got an example source in C#? I have those texthooking working, but can't get the window detection (and resolving the client) to work in C#.. Would be very welcome to get some help or source Smile

Robert on 4/16/2009 3:53 AM (328 days ago)

I'm havn't ever coded in C, but am a pretty good AutoHotKey coder. Is there any way to implement this in AHK? I don't want to build a bot, I'm just trying to read the board. (I'd really rather not have to learn C++/C# etc!).

thx!

Anonymous on 4/18/2009 6:27 PM (326 days ago)

The number of players that Stars shows is not the number of people playing, it's the combined number of occupied seats. The actual person to table ratio is closer to 1:3 than 1:7.

Orwellophile on 4/23/2009 6:40 PM (321 days ago)

I just tried running this, and the Pokerstars chat is no longer being displayed. Has PS updated so it no longer works? Frown

P1 on 4/27/2009 12:49 PM (317 days ago)

Hey, looking over your original code and I am trying to make some additions, including removing the doubling effect that occurs in Full Tilt Poker, but I have limited knowledge of such indepth programs. Could someone help me out by telling me where in the original source code I can find a) where it actually outputs the data found to the table, b) where it 'decodes' the windows api text call into english, and c) the name of the variables of both the table name and the parameters (the text itself). I have spent hours looking through this group but cannot find how to control the output of these things.

Thank you!

Jim on 6/10/2009 4:34 PM (273 days ago)

Does this still work after the Full Tilt update around the 11th of July?

I think that FT changes the names of various software parameters on their tables as the tables are created (in real time)..

Tinker on 7/17/2009 2:01 PM (236 days ago)

Hi duys

Have some problems building the Xbot. To be honest I don't know what should I do. Know nothing about C at all. What should I look up to run the bot or maybe someone can write a step- by step tutorial/plan? The only language I know is AutoIt. Need help here Smile

P.S. James, you must be a nice guy to give away all the base of knowledge you were collecting for so many years. Thanks a lot

Misha on 8/14/2009 4:45 PM (208 days ago)

It asks me to specify the executable to be used in the debug session. Hope someone will help Smile

Misha on 8/14/2009 4:47 PM (208 days ago)

Hi!

Sadly after the major FT change in client software the monitor bot code stopped working://
James, do you know how to make it work? I noticed they started using QT libs - every piece of GUI appears as QWidget in Spy++...

Rudy on 10/8/2009 9:20 PM (153 days ago)

Is their any way to patent this code?

fish tanks for sale on 10/9/2009 12:53 AM (152 days ago)

the PokerStars chat is not displayed

when a upgrade of the program??

ezio on 10/15/2009 7:15 AM (146 days ago)

in the log file you can read something like this:

[CODE]
MSG_TABLE_SUBSCR_DEALPLAYERCARDS
sit0
nCards=2
sit1
nCards=2
sit2
nCards=2
sit4
nCards=2
sit5
nCards=2
sit6
nCards=2
sit7
nCards=2
sit8
nCards=2
dealerPos=0
TableAnimation::dealPlayerCards
Game #33999204260 00020528
OnTableData() round -1
MSG_TABLE_PLAYERCARDS 00020528
::: 8c
::: 10c
------ 00020528
[/CODE]

it's easy to estract my hole cards... but it' s also possible to know my position respect to the button?

initially I thought that this info could be taken from the "dealerPos" value, but this number seems to be random: I can be at BB position with several dealerPos numbers...

ezio on 10/15/2009 7:28 AM (146 days ago)

precise statement to the previous post: "I can be at BB position with several dealerPos numbers"... obviously standing always at the same seat... so that I really can't understand the sense of this log entry

ezio on 10/15/2009 1:20 PM (146 days ago)

Problems:
1. No info shows in PStars chat window.
2. It wouldn't start PStars until I add full path to
DetourCreateProcessWithDll( pokerClientPath, NULL, NULL, NULL, TRUE,
CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED, NULL, NULL,
&si, &pi, "H:\\Tank\\XMonitor\\bin\\detoured.dll",
"H:\\Tank\\XMonitor\\bin\\XPokerBot.Hook.dll", NULL);

Any suggestions?
Also, is there a place, with latest versions of code - in case UI changes in apps you spy upon?

Anonymous on 11/6/2009 8:17 AM (124 days ago)

More problems. When debugging the problem with "no info in chat window", I found, that InstallMonitors(); is never called.

Anonymous on 11/6/2009 11:42 AM (124 days ago)

No author answers to the questions... is this site out?

The Game chat text window of the program remains empty: when a upgrade of the code to display the chat text?

ezio on 11/7/2009 7:53 AM (123 days ago)

Impressive program! Have the above problems been worked out?

mucinex dm on 11/14/2009 3:17 PM (116 days ago)

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.

Poker of Aces on 11/22/2009 9:49 PM (108 days ago)

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

James on 12/11/2009 10:07 AM (89 days ago)

Also very nice pictures here.

Unibet on 12/14/2009 1:12 AM (86 days ago)

There is a marvelous amount of job in that. Reading the screen is now one part of it, creating the output, managing the different tables, writing good poker policies? Did you notice the rule editor he had programmed?

online casino bluebook on 12/26/2009 5:11 AM (74 days ago)

There is a marvelous amount of job in that. Reading the screen is now one part of it, creating the output, managing the different tables, writing good poker policies? Did you notice the rule editor he had programmed? online casino bluebook

Taylor on 12/26/2009 5:15 AM (74 days ago)

Just in case you want to own PokerStars - you can buy it:
www.professional-poker.com/.../...ars-for-sale.htm

Anonymous on 1/3/2010 8:27 AM (66 days ago)

Hi, and thanks for a great article.
I have tried the MonitorBot and the game chat text doesn't work.
From the log file snooping I find the hole cards, but the value of the flop etc. Can't be found there.
I have changed the dealer settings to verbose.
Anyone else who have the same problem?

Best Regards,
David

David on 1/3/2010 11:41 AM (66 days ago)

Poker bots are computer programs that play poker. In terms of ability to play online that can perform almost as an average player. This is because the game online is very different from the face to face version.

Best Regards,
Online gambling

Johnny Ferrell on 2/3/2010 6:32 AM (35 days ago)

hi,

Online Casino – We review and guide to blackjack sites to let you know where to play cash and free blackjack games to earn big deal. To know more, log on to: http://www.mrblackjack.com/

Casino Master on 2/8/2010 5:49 AM (30 days ago)

Hi,

Don’t go all online casino craps. Play on safe and reliable Online Craps game sites. Follow our casino craps review, offers and bonuses to get real information on Real craps game online. To know more, log on to: http://www.mrcraps.com/

Craps on 2/8/2010 5:50 AM (30 days ago)

hi,

Play casino game at Mrroulette.com. Enjoy playing this exciting game with Mrroulette.com, A place for most favorite online roulette game site. To know more, log on to: mrroulette.com

Gambling Master on 2/8/2010 5:53 AM (30 days ago)

Wow! nice and helpful. By the name of fulltilt rakeback I can remember that now a top rated poker site also offers such games. Just check out!

Poker on 2/8/2010 2:59 PM (30 days ago)

Hi,
I have read all of your post, in this way I found you have tremendous guts on online poker games. I want some tips from you to built a rakeback poker gaming bot. Though I am already involved in a online poker site that offer good amount of rakeback instant.

Mark on 2/25/2010 7:13 AM (13 days ago)

Hey friend,
You have good knowledge on online poker games. But I want to know that is your process applicable for casino jackpot games too? Actually I do webmaster for a renowned casino review site, but very eager to built a site like mrjackpot.com

Hope will get some help from you.

Mac on 2/26/2010 6:32 AM (12 days ago)

Hi,
Your post mostly technical. But I love your dedication to your work. And the most important thing is that you have discussed it with us, thank for that.


Online Videopoker | Onlinebingo

Jenny on 2/27/2010 12:19 PM (11 days ago)

Hi,
Your post mostly technical. But I love your dedication to your work. And the most important thing is that you have discussed it with us, thank for that.


Online Videopoker | Onlinebingo

Jenny on 2/27/2010 12:19 PM (11 days ago)

Hi,
I am very much intersted on your technique. As I do webmaster for a online casino site, I know some thing about casino and poker games. Apart from this I am involved with a popular site, here people can url=http://www.bunnybingo.com/]play bingo[/url] games and get a chance to get many prizes. In personal life I have dreamt that can build a poker bot like you. Your post give me a boosting power. Thanks for sharing.

Joseph on 2/27/2010 3:34 PM (11 days ago)

Hi,
I am very much intersted on your technique. As I do webmaster for a online casino site, I know some thing about casino and poker games. Apart from this I am involved with a popular site, here people can play bingo games and get a chance to get many prizes. In personal life I have dreamt that can build a poker bot like you. Your post give me a boosting power. Thanks for sharing.

Joseph on 2/27/2010 3:35 PM (11 days ago)

Comment on this post:

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