How to check that a player's PC meets your requirements

Introduction

Generally, when you write your game, very little thought will initially be given to system specifications. The usual train of thought might be "well it runs on my system, so i'll publish this as the minimum or recommended specs in a readme file, or on my website".
However, what will your game do if the player's PC fails to meet your expectations? This article will outline what sort of things you should be checking for, and when.
There is a decidedly windows and DirectX slant to this article as these are my platforms of choice. The concepts are transferable to other platforms and frameworks, however, the source code i give here is not.

Why should i even attempt to detect system specifications?

It gives a good impression

Checking for the user's system specification will ensure that all users who can run your game will run it without issue, and those who cannot run it will be presented with something useful. A game which crashes or even worse, takes down the player's system when they try to start it, with no reason or rhyme as to why will discourage them from trying again, and what's worse they might even go onto Twitter and disparage your game's name. One player spreading bad news about your game is enough to deter many other potential players.

It cuts down on support issues

If the player receives a well thought out and instructive error message in the event of a failure, they will know who to contact, and how. The error message could even advise them on what they need to do next before they call you, e.g. to purchase a better graphics card, or delete some programs to free up space, or even to change their display resolution. If they aren't told this beforehand, they will have to contact someone. That someone might be you, and this is your time they will take up which is better spend producing more games.

It helps with system stability

Checking for the correct capaibilities beforehand will cut down on the amount of crashes that a player might encounter if their machine isn't quite up to par. As outlined above, a game which crashes is a bad thing, but worse than that, a complete system crash (e.g. by switching to full screen mode with no easy way out) might risk other data on the user's machine, causing damage as well as annoyance.

How and when should i detect system specifications?

You should attempt to detect system specifications whenever your game starts. This should preferably be done before any systems are properly initialised, so that windows is still in a state where the user can properly click any error messages away and get back to what they were doing before trying to run your game.
In my own game, I have split system specifications detection into several classes, each of which is responsibile for detecting the state of one subsystem. Each is called in turn, with the simplest checks done first as some depend on others for success.
It is best to leave the code which checks for system specifications till last in your game, as you won't know what specifications your game needs until this point and are likely to go back and change it repeatedly, otherwise.
Important subsystems to check are:

System RAM - is there enough to run the game?

CPU speed - is the CPU fast enough? Is it multi-core?

Hard disk space - is there enough for save game files and other data you might put there?

Hard disk speed - will your game fall over when streaming assets from disk?

GPU speed and video RAM - Is the graphical performance of the machine sufficient?

Network connectivity - Is there a network connection? Is the internet reachable, e.g. to look for updates?

Windows version - Is the version of windows up to date enough to do what you need to do?

I will cover a subset of these checks here, and recommend where you can find code for the others, as to cover every possible thing you might want to check is beyond the scope of this article as many things are system specific.

Checking system RAM size

You can check the system RAM size on windows using the GlobalMemoryStatusEx() function, which will tell you amongst other things the amount of free and total RAM, and the amount of free and total pagefile:
const ONE_GIG = 1073741824;
MEMORYSTATUSEX status;
ZeroMemory(&status);
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
if (status.ullTotalPhys < ONE_GIG) {
MessageBox(0, "You don't have enough RAM, 1GB is needed", "Epic Fail", MB_OK);
exit(0);
}

Detecting the windows version

Detecting the windows version may be important if you only wish to support certain types of installation. For example, you might not want the user to run your game on a server, or you might want to ensure, before your game even tries to access DirectX, that they are not running windows XP or earlier if this will have an impact on your game.
Detecting the version information of windows is very simple and should be done using the GetVersionEx win32 function:
WindowsVersion::WindowsVersion() : Checkable("windows/version")
{
OSVERSIONINFOEX vi;
ZeroMemory(&vi, sizeof(OSVERSIONINFOEX));
vi.dwOSVersionInfoSize = sizeof(vi);
GetVersionEx((LPOSVERSIONINFO)&vi);
vMajor = vi.dwMajorVersion;
vMinor = vi.dwMinorVersion;
spMajor = vi.wServicePackMajor;
spMinor = vi.wServicePackMinor;
Build = vi.dwBuildNumber;
Platform = vi.dwPlatformId;
ProductType = vi.wProductType;
}
bool WindowsVersion::IsServer()
{
return (ProductType == VER_NT_DOMAIN_CONTROLLER || ProductType == VER_NT_SERVER);
}
bool WindowsVersion::IsGreaterThanXP()
{
return (Platform == VER_PLATFORM_WIN32_NT && vMajor >= 6);
}
Please note, however, that there is an important gotcha to this function call. You cannot use it to detect if the user is running windows 8.1, only version 8.0. This is because the call will only return the newer version number if your executable embeds the correct manifest. If you want to detect this, you should use the newer Version helper API from the Windows 8.1 SDK instead. If all you want to do is detect XP, or anything older than windows 8.0, then GetVersionEx will do fine.

Detecting hard disk space

Detecting hard disk space is relatively simple, and can be done via the GetDiskFreeSpaceEx function. You should always avoid the simpler GetDiskFreeSpace function, which operates in number of clusters rather than number of free bytes, taking more work to get a simple answer rather than just returning a simple 64 bit value you can check.
Using this function is very simple:
INT64 userbytes;
INT64 totalbytes;
INT64 systembytes;
BOOL res = GetDiskFreeSpaceEx(L".", (PULARGE_INTEGER)&userbytes, (PULARGE_INTEGER)&totalbytes, (PULARGE_INTEGER)&systembytes);
std::cout << "Your disk has " << userbytes << " bytes available for use.";
Note the difference between userbytes and systembytes in the example above. The userbytes value is the amount of disk space available to the current user, as the disk might be limited by a quota. The systembytes is the total space ignoring quotas, available to all users. Therefore, you should usually check the first result field.

Detecting CPU speed

There are many ways to detect the CPU speed. Some of the more common ones are:

Using a busy loop to calculate CPU - mostly deprecated as this is extremely hard to get right on multi-tasking operating systems, and not recommended outside of kernel level code

On most modern systems, you are more likely to run into problems with lack of RAM, or lack of a good graphics card before you encounter problems with CPU performance. Your mileage may vary but in my own experience, less time needs to be spent on detecting the CPU speed and more time on other factors as CPU speed is greatly affected by what else is running at the time and how much swapping the system might need to do.

Conclusion

The advice above should help you detect your player's specifications effectively. This is of course just the tip of a very big iceberg, and once you start detecting various metrics from a player's system, you will keep thinking of other things you really should check. Don't get carried away though, as it is easy to get distracted trying to detect potential edge cases, rather than just carrying on with the game.

Share this comment

Link to comment

Share on other sites

I don't think it's a good idea to force exit when specs are not met. I remember some app complained about -1B of ram once, when I had like 4GB

This is why you should use GlobalMemoryStatusEx() as listed above, which returns a 64 bit counter of number of available bytes, rather than the older 32 bit calls which are clamped to a max of 4gb, less if they subtract the memory reserved for kernel use...

Share this comment

Link to comment

Share on other sites

It's nice to inform the users that they don't meet the minimum specs, and nice to tell them in what way they don't, but you don't want to force the user to exit the program if your software thinks it won't be able to run. Users should still be able to try.

And having a minimum required number of CPU cores doesn't make too much sense - Maybe the "minimum spec" for the game is two cores at 1.2ghz... and maybe a single-core 2.8 ghz would actually run the game better, but gets locked out because the developer enforced a minimum number of cores. 1 core doesn't equal 1 thread anyway.

Sometimes being too clever can work against you. Your program giving intelligent suggestions is good, but rigid enforcement of those suggestions might be less so.

Share this comment

Link to comment

Share on other sites

No mention of checking the specs at installation time? I would personally hate to find out that I can't run your game or may not get the full experience after I've gone through the installation.

I see the need to check things at startup to account for changing conditions, but installation time is still the best time to warn the user about potential issues. All of the tips/code you've presented should probably be incorporated into the installer as well.

Share this comment

Link to comment

Share on other sites

@davehunt I agree on principle that the same checks should be done by the installer at install time.

However in this day and age does anyone write a custom installer rather than using nsis, installshield or the like? These do a lot of these checks for you in a simple scripted or RAD style manner so you won't need to know how to check them in C++ there...

Share this comment

Link to comment

Share on other sites

This sounds a bit less tidy than the directx way. What do you do if it's an intel card for example?

Surely there is a more portable way?

There isn't that I know of, OpenGL doesn't provide information about the memory usage on the GPU, the only way we're able to know is due to vendor specific extensions and I don't think Intel has such an extension or at least that I can find.

Share this comment

Link to comment

Share on other sites

[quote name="Joshhua5" timestamp="1426813614"][quote name="braindigitalis" timestamp="1426780063"]
This sounds a bit less tidy than the directx way. What do you do if it's an intel card for example?
Surely there is a more portable way?
[/quote]
There isn't that I know of, OpenGL doesn't provide information about the memory usage on the GPU, the only way we're able to know is due to vendor specific extensions and I don't think Intel has such an extension or at least that I can find.[/quote]
It sounds to me like on windows you might be best using my method If you wanted to check video ram then, and then instantiate opengl afterwards? At least the DirectX way cooperates with all known hardware...
As for other platforms I bet you could get this information from /proc on Linux...

Share this comment

Link to comment

Share on other sites

Share this comment

Link to comment

Share on other sites

The problem with installation-time checks.is that many game installs are silent when using a system like Steam.

It'd be great if Steam itself provided an optional "Can I even run it?" test for a game before you even buy it... though I suppose an argument can be made that that's one of the purposes of game demos.

Share this comment

Link to comment

Share on other sites

The problem with installation-time checks.is that many game installs are silent when using a system like Steam.

It'd be great if Steam itself provided an optional "Can I even run it?" test for a game before you even buy it... though I suppose an argument can be made that that's one of the purposes of game demos.

Yea, but how many games on Steam do offer a demo?

@braindigitalis: Good article =) I think a good addition would be another one about how to actually find out your minimum/recommended specs.

Share this comment

Link to comment

Share on other sites

Forcing to exit the program will do harm in systems with rare hardware that returns values you don't expect or in future versions of Windows. I still remember programs that refuse to install because they think my hard drive is full (they can't handle >120GB drives), I don't meet the memory requirements (it doesn't know how to deal when you have >512MB RAM) or think my DirectX version is too old.

You may think your code is flawless but there's a high chance it won't work somewhere in someone's machine, and is nearly impossible to make future-proof (what if "drives/disks" no longer exist as such in 10 years?).

GetDiskFreeSpaceEx on the current folder is a bad idea because the program may be launched with different "Start from folder" parameters. DLLs can also change this. You should at least enumerate all disks.

Video RAM size needs to be treated with care. For example, Intel cards will return absurdly low dedicated RAM sizes (<64MB) because they rely on shared memory, since it's basically the same thing as the integrated card is using the system memory anyway. Dedicated RAM is just the amount of memory that the card has exlusively reserved for the GPU chip; which is used for the front buffer, and useful in the cases where the OS is low on memory (the OS can't reclaim that reserved chunk).

To be honest, you shouldn't base your decisions on Video RAM because the returned values are almost meaningless due to high variety of hardware (dedicated cards, Optimus, Intel integrated, AMD APUs).

It's nice to have for a bug report, but you shouldn't make your application take decisions based on it (if it does, at least users should be able to override them).

PS. "Epic fail" was funny. But shouldn't actually be part of the program. It's really rude to tell a user that he or his machine is an "epic fail". They may not take it well.

Share this comment

Link to comment

Share on other sites

Yeah - Don't force the user to exit but give the user a choice. Sometimes the OS will provide a work-around which is satisfactory such as page file size overflow or the performance reduction is tolerable to the player.

Share this comment

Link to comment

Share on other sites

I don't think it's a good idea to force exit when specs are not met. I remember some app complained about -1B of ram once, when I had like 4GB

Exactly, I still remember games that refused to play because they relied on functions reporting up to 2GB of disk space. I knew I had an awful lot more than that but the installer just didn't give an option and exited.

Always let the user have the final decision.

Share this comment

Link to comment

Share on other sites

This sounds a bit less tidy than the directx way. What do you do if it's an intel card for example?

Surely there is a more portable way?

There isn't that I know of, OpenGL doesn't provide information about the memory usage on the GPU, the only way we're able to know is due to vendor specific extensions and I don't think Intel has such an extension or at least that I can find.

It sounds to me like on windows you might be best using my method If you wanted to check video ram then, and then instantiate opengl afterwards? At least the DirectX way cooperates with all known hardware...

As for other platforms I bet you could get this information from /proc on Linux...

I know this is kinda old, but if you are using Windows, there is one way, but I know you and everyone else won't like it... and that is to use DirectDraw.

Now, you don't have to fully initialize DirectDraw, and you can do this when using OpenGL without any interference between the two APIs (I've done it multiple times in the past).

This will work fine, as long as you just use functions like GetAvailableVidMem() or WaitForVerticaBlank(). Don't do anything like try setting the cooperative level, set the display mode or start creating surfaces with it, okay?