Open Side Menu Go to the Top
Register
How do tablenninja and sharky get pokerstars lobby info? How do tablenninja and sharky get pokerstars lobby info?

05-10-2013 , 03:01 PM
Hi guys,

I'm having a look at writing my own custom table selector for pokerstars, and i need to be able to grab information about all the tournaments opening up in the main lobby window. I have used Spy++ to get the window handle of the tournament list but its a custom list so i'm kinda stumped as to where to go!

Also, how can you find out a list of the players currently sitting at a table? Tableninja and sharky both seem to do this without having to have the information on screen???

Has anyone attempted this before? Any ideas?

Thanks.
How do tablenninja and sharky get pokerstars lobby info? Quote
05-13-2013 , 05:15 AM
Ok, after some research i have answered my own question.

It seems that the best way is by reading the PokerStars process memory directly, i checked with pokerstars that this is ok (i dont want to get banned!) and they said its fine as long as im not making anything that would break their terms and conditions (which im not).

So currently i can get read access to the process memory, does anyone have any experience grabbing things from an active processes memory? Ill keep snooping around but if anyone has any tips that would be great.

Thanks
How do tablenninja and sharky get pokerstars lobby info? Quote
05-14-2013 , 01:10 PM
Quote:
Originally Posted by 0bl1ky
So currently i can get read access to the process memory, does anyone have any experience grabbing things from an active processes memory? Ill keep snooping around but if anyone has any tips that would be great.
Here is the code that was used for FPHG to grab hands from Party's memory, it might be helpful:
Code:
	void PartyMemoryScanner(const char* LobbyTitle)
	{	// This function will scan all of the party memory looking for new Hand Histories,
		// and do all the required work when found.
		// Returns false if something wrong, else true.
		// v0.04b: Can now handle multiple copies of Party in the same user area.
		// v0.05: Can now take different lobby titles, so will work on Empire also.

		// Process ID of the window.
		DWORD PID;

		// Used to find the allowable range of memory for the process.
		SYSTEM_INFO SI;

		// Used to check each mem segment's properties.
		MEMORY_BASIC_INFORMATION MBI;

		// This is used so we don't keep reallocating this.
		static char* Buffer = NULL;
		static DWORD BufferLen = 0;

		// Window handle for party processes.
		HWND WH=NULL;				// Init to NULL for FindNextWithPrefix() loop.

		// 0.04b: Loop through all of the party poker processes.
		while ((WH=FindNextWithPrefix(WH,LobbyTitle))!=NULL) 
		{

			// Get the PID of the window.
			GetWindowThreadProcessId(WH,&PID);

			// Open the process.
			HANDLE hProcess = OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION,false,PID);

			// Determine applications memory addresses range.
			GetSystemInfo(&SI);

			// Set to the start/base address.
			void* CurrentAddress = SI.lpMinimumApplicationAddress;

			// Keep searching until we got to the end of memory chunks.
			while (CurrentAddress <= SI.lpMaximumApplicationAddress) {

				// Set to 0, just in case this fails.
				MBI.RegionSize = 0;

				// Try to query the memory region.
				DWORD Ret = VirtualQueryEx(hProcess,CurrentAddress,&MBI,sizeof(MEMORY_BASIC_INFORMATION));
				if (Ret == sizeof(MEMORY_BASIC_INFORMATION)) {

					// Is this the type of memory region where we hand hands?
					if (MBI.Type == MEM_PRIVATE && MBI.State == MEM_COMMIT
						&& MBI.AllocationProtect == PAGE_READWRITE
						&& MBI.Protect == PAGE_READWRITE
						&& MBI.RegionSize >= MIN_PAGE_SIZE) {

						// None read yet.
						DWORD NumRead=0;

						// Do we need a bigger buffer?
						if (MBI.RegionSize>BufferLen) {
							if (Buffer!=NULL)
								delete [] Buffer;
							Buffer = new char[MBI.RegionSize];
							BufferLen=MBI.RegionSize;
						}

						// v0.03: Read the memory - Try many times in case locked, then give in...
						int FailCount=0;
						while (!ReadProcessMemory(hProcess,MBI.BaseAddress,Buffer,MBI.RegionSize,&NumRead)) {

							// Failed again.
							FailCount++;

							// Just incase we get stuck in a loop forever...
							if (FailCount==MAX_FAIL_RPM_COUNT) {
								//Error(WARNING,"FreePHG: ReadProcessMemory() failed, even with retries.");
								NumRead=0;			// So we will have to skip this.
								break;				// No more trying.
							}

							// Sleep a little to give party a chance to unlock the mem.
							Sleep(FAIL_RPM_SLEEP);

						};

						// v0.06c: Remove all EVEN bytes (remember that now string allignment needs to be set to 4).
						for (unsigned int I=0;I<BufferLen;I+=2)
							Buffer[I/2]=Buffer[I];
						NumRead/=2;

						// Have we at least got a big enough region to be useful
						if (NumRead>=strlen("#Game No : ##########")) 
						{
							//  Check if region contain search string.
							// v0.03: Optimized (Crappy VC6++ compilier don't like strlen in for)...
							//        - Note: Using Boyer-Moore etc, not gonna help much here...
							unsigned int MaxLen=NumRead-strlen("#Game No : ##########");
							for (unsigned int I=0;I<MaxLen;I++) {

								// Look for "#Game No : " - Don't use stncmp - slow as ****...
								// v0.04: Use the fact always alligned to just check via a 32bit comp.
								//        NOTE: Will even cast to non-aligned int, but will be slower.
								if ((*((unsigned int*)(&Buffer[I]))) == ((((unsigned int)'m')<<24)
									|(((unsigned int)'a')<<16)
									|(((unsigned int)'G')<<8)
									|(((unsigned int)'#'))) // Remember: big-endian...
									&& Buffer[I+4]=='e'
									&& Buffer[I+5]==' '
									&& Buffer[I+6]=='N'
									&& Buffer[I+7]=='o'
									&& Buffer[I+8]==' '
									&& Buffer[I+9]==':'
									&& Buffer[I+10]==' '
									&& Buffer[I+11]>='0'
									&& Buffer[I+11]<='9') 										{

									// Does it have the stars?
									if (!(Buffer[I+23]=='\n' && Buffer[I+24]=='*') 
										&& !(Buffer[I+24]=='\n' && Buffer[I+25]=='*')) {
										continue;
									}

									// Find the end of the hand.
									for (unsigned int J=I+1+strlen("#Game No : ##########");J<NumRead;J++) 
									{
										// Check we have no garbage characters (ie: only allow ASCII char, CR/NL or accented) before terminator.
										if (Buffer[J]!=10 && Buffer[J]!=13												// NL or CR.
											&& Buffer[J]!=128 && Buffer[J]!=174											// v3.04: '€' or '®'.
											&& !(Buffer[J]>=32 && Buffer[J]<=127)										// Normal ASCII characters.
											&& !((unsigned char)Buffer[J]>=0xC0 && (unsigned char)Buffer[J]<=0xFF)) {	// Accented characters.
											break;
										}

										// Terminate on "\r\n\0" if in realtime mode.
										if (Buffer[J-1]=='\r' && Buffer[J]=='\n'&& Buffer[J+1]==0) {
											HandleTableData(&Buffer[I],(J-I));				// @@@BUG: Note we don't count the null terminator here!
											break;
										}

									}

								} // End match "#Game No : ".

							} // End Find HH loop.

						} // End if (NumRead>=strlen("#Game No :  ").

						// ------------------------------------------------------------------#
					}

				}

				// Increase base address for next searching cicle.
				// NOTE: We were using a DWORD here rather than a SIZE_T which might explain why it didn't work on 64bit systems.
				CurrentAddress = (void*)((SIZE_T)MBI.BaseAddress+(SIZE_T)MBI.RegionSize);

			} // End while more chunks.

			CloseHandle(hProcess);

		}	// While loop to scan multiple party processes (if they exist).

	} // End PartyMemoryScanner.
Helpful things I remember from doing this:

* Start off writing something to dump all the process memory to a file or files and then you can work through it using a hex editor to try to figure out what you are looking for.

* Search for both ASCII and UNICODE type strings.

* Once you find what you are looking for, use the MEMORY_BASIC_INFORMATION values to try to narrow down what type of segments you need to search (see above "if (MBI.Type == MEM_PRIVATE ..." part - MIN_PAGE_SIZE was 64k IIRC). This will speed up the amount of time you spend on each memory pass significantly.

* Similar to the above point, try to work out if the values are aligned to always start on a 2, 4, 8, 16, etc byte boundary... This will let you iterate much quicker (IIRC Party started off aligned to 8-byte boundaries but then they changed something and it stopped being aligned).

* Don't bother using a complex string search algorithm (eg: Boyer–Moore, memmem, etc) - just cast the first 4 (ASCII) or 2 (UNICODE) to a single 32bit value (remember to reverse the endianess) and use that to check like I did in the code above. I tried several string search algorithms and they were all 2-3x slower than this simple method for this task...

* Sometimes ReadProcessMemory() fails (I think when the process itself locks a page or something) so make sure you try several times with a small 20-50ms sleep between calls (see above code).

* Don't convert all the characters to ASCII from UNICODE like I did above lol - that was just a crappy hack to unbreak the memory searching code work after Party changed from using ASCII to UNICODE characters (FPHG actually was changed to work by hooking memcpy() shortly after this so it was never really needed).

If you still can't figure out what you need to grab then you can look into hooking windows' GDI textout functions (or whatever stars is using). It's not very easy to work out which window/table goes with which HDC though (backbuffering, etc) so best to try scanning memory first.

Hope this helps

Juk

EDIT: This might not work well on 64bit OSes - it was written way before anybody used 64bit OSes and I see I've hacked some bit of it to use SIZE_T to make it work in 64bit OS scanning a 32bit process, but you'll prolly have to do some more reading to grab from a 64bit process, etc.

Last edited by jukofyork; 05-14-2013 at 01:17 PM.
How do tablenninja and sharky get pokerstars lobby info? Quote
05-14-2013 , 05:16 PM
thanks a bunch for the info juk

also, i'm pretty sure that TableScan Turbo uses OCR, thats another alternative
How do tablenninja and sharky get pokerstars lobby info? Quote
05-14-2013 , 07:57 PM
Juk, thanks alot for the advice! I should have some time to look over this tonight and ill post my progress on this if anyone is interested.

Also, if anyone wants to do something similar i would suggest starting by looking here:

http://forumserver.twoplustwo.com/45...mining-353393/

and

http://blackandodd.blogspot.co.uk/20...memory-in.html
How do tablenninja and sharky get pokerstars lobby info? Quote
05-16-2013 , 05:22 PM
Seems like the three methods would be
1) Memory analysis
2) Packet sniffing/analysis
3) Some sort of scraping of the client (from the GUI/table)
How do tablenninja and sharky get pokerstars lobby info? Quote
05-16-2013 , 06:26 PM
i highly doubt packing sniffing will yield anything worthwhile, thats probably all encrypted
How do tablenninja and sharky get pokerstars lobby info? Quote
05-16-2013 , 07:37 PM
Quote:
Originally Posted by greg nice
i highly doubt packing sniffing will yield anything worthwhile, thats probably all encrypted
I think you can hook SSL_read() on some sites (ie: to get the data right after it's been unencrypted), but IIRC somebody said in another thread that Stars deliberately statically link with the SSL functions to stop this.

Juk
How do tablenninja and sharky get pokerstars lobby info? Quote

      
m