Tinkering with Tinker: How Vista Ultimate is detected

There seems to be a misconception, in the normal user population, that Windows Vista, Ultimate Edition, provides some sort of extra kick that Ultimate Extras need to run properly. This isn’t the case. In fact, the requirement is purely arbitrary, from a technical standpoint.

While Microsoft partner Oberon Games took the more officially-undocumented SLGetWindowsInformationDWORD route to determine whether or not to run (Hold'Em), Fuel Games took the more traditional route.

Upon cursory glance of the Tinker executable, I noticed it imported the GetVersionEx and GetProductInfo APIs from kernel32.dll. For those playing at home, GetProductInfo wasn’t explicitly imported but rather done at runtime using LoadLibrary/GetProcAddress. Why? I’m not sure.

It’s easy to rule out GetVersionEx because, as documentation shows, it does not provide enough information to make a determination as to which SKU of Windows you’re running. GetProductInfo, on the other hand, does and was specifically engineered for this task. We’ll see its use in a second.

I fired up WinDbg and attached it to Tinker.exe (File—>Open Executable), set a breakpoint on GetProductInfo (bp GetProductInfo) and ran the game (g).

After a few seconds of runtime, my breakpoint was hit and the debugger shot us into the first instruction within the function.

I stepped out (gu) of the GetProductInfo function as its inner workings are of no interest to me today and got a higher level picture of what this program is doing. Let’s take this apart, piece by piece.

00000000`00f71787 push    11Ch
00000000`00f7178c lea     eax,[ebp-744h]
00000000`00f71792 push    ebx
00000000`00f71793 push    eax
00000000`00f71794 call    Tinker!memset (00f74cb2)
00000000`00f71799 add     esp,0Ch

00000000`00f7179c lea     eax,[ebp-744h]
00000000`00f717a2 push    eax
00000000`00f717a3 mov     dword ptr [ebp-744h],11Ch
00000000`00f717ad call    dword ptr [Tinker!_imp__GetVersionExW (00f5107c)]


osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

As the GetProductInfo function requires a bunch of version numbers to be passed, the game first calls GetVersionEx, passing a pointer to a chunk of zeroized memory to receive an OSVERSIONINFOEX block of information.

00000000`00f717b3 lea     eax,[ebp-760h]
00000000`00f717b9 push    eax
00000000`00f717ba movzx   eax,word ptr [ebp-62Eh]
00000000`00f717c1 push    eax
00000000`00f717c2 movzx   eax,word ptr [ebp-630h]
00000000`00f717c9 push    eax
00000000`00f717ca push    dword ptr [ebp-73Ch]
00000000`00f717d0 push    dword ptr [ebp-740h]
00000000`00f717d6 call    dword ptr [ebp-754h]

DWORD dwProductType = 0;
BOOL bRet = 
 GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, 1, 0,&dwProductType);

Now having the platform's major and minor build/service pack numbers, all this information is passed neatly to the GetProductInfo function. As MSDN states, a non-zero number is returned if everything checks out (true) otherwise a zero is returned (false). That function will also write which SKU we're running into a DWORD variable passed in by reference.

00000000`00f717dc cmp     dword ptr [ebp-740h],6 ss:002b:00000000`001cee84=00000006
00000000`00f717e3 jne     Tinker!SparkEngine::Init+0x1b1 (00f717ed)
00000000`00f717e5 cmp     dword ptr [ebp-73Ch],ebx
00000000`00f717eb je      Tinker!SparkEngine::Init+0x1b7 (00f717f3)
00000000`00f717ed mov     byte ptr [ebp-745h],bl
00000000`00f717f3 cmp     dword ptr [ebp-760h],1
00000000`00f717fa je      Tinker!SparkEngine::Init+0x1cd (00f71809)
00000000`00f717fc cmp     dword ptr [ebp-760h],1Ch
00000000`00f71803 jne     Tinker!SparkEngine::Init+0x415 (00f71a51)
00000000`00f71809 cmp     byte ptr [ebp-745h],bl
00000000`00f7180f je      Tinker!SparkEngine::Init+0x415 (00f71a51)

if(osvi.dwMajorVersion != 6 && osvi.dwMinorVersion != 0)
 bSupported = bRet;

if(bSupported && (dwProductType == PRODUCT_ULTIMATE || dwProductType == PRODUCT_ULTIMATE_N)) {
  /* Spin up game, supported operating system! :) */
} else {
  /* Spin down game, unsupported operating system :( */

This confusing block of assembly is the meat of the check. It handles the checking of the SKU output from GetProductInfo. Note the smart check for Ultimate N SKUs -- I would've totally forgotten about "them". I wonder if this covers the Ultimate K SKU... (Korea)

That's it.

Although this SKU check is far from complex, I'm curious as to why Tinker wasn't tied to the Microsoft-Windows-Shell-PremiumInboxGames licensing 'feature' like Texas Hold'Em. A simple call to SLGetWindowsInformationDWORD would have sufficied...

Note to Fuel Games: Thanks to Known DLLs, you don't need to ascertain the full path to kernel32.dll and do a bunch of string concatenation trickery. Simply use: LoadLibrary(_T("kernel32"));