This is part 1 of a 100 part series that pulls apart the Quake I engine (as represented by the Fitzquake remake) and examines it as we rebuild it. Here are the 5 functions we are adding to the project today:
- WinMain() – the main function for any Windows function
- Sys_Error() – a general way for any Quake function to report an error
- Sys_FloatTime() – a way to track how much time has passed in the game
- Q_strlen() – a custom string length function
- COM_InitArgv() – sets up any arguments passed to Quake to be checked by the system
These functions are not going to work 100% when we’re done, because they call further functions that we don’t have a chance to get to yet. But added up they total roughly 400 lines of code, which is what we need to average to completely rebuild Quake in 100 sessions.
The first thing you may want to do is download today’s material. The following file takes the base code from part 0 and adds the 5 functions listed. There are two versions – a compile-ready version that have all the undefined portions of these functions commented out. The CLOC-ready version leaves these lines in, so that you can run CLOC to see how many lines of code have already been defined.
As you can see from my running of CLOC, this adds 380 lines to the project, which is right on track to finish this project in 100 steps. We now have 4,542 of our lines of code typed in, which brings the total up to 10.7%. Sadly, however, the program still doesn’t do anything visually – it just runs and shuts down.
Also, as you may have guessed from the names of the functions, there is much more maintenance code here than there is actual game code. These posts are coming from a game engineering background, not a strictly game programming background, so we’ll get to entity states and graphics later. Each lesson will begin with code that is ready for you to compile and look at, so you can jump ahead to lesson 10 or 20 or wherever the game work eventually begins, and you should be able to pick up fairly easily.
If you’re a beginner programmer however, and you want to see how everything comes together, then this is where to start. You may want to use the compile ready version of the code, as this is the version that has only the code we are currently looking at inserted in it. If you can understand everything that is compiling in this version, then you have understood this code.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
This is the function where any Windows application begins. In Quake it is about 200 lines long, and goes through 3 general steps.
- 1. Initialize all the systems of the program.
- 2. Run the game loop, and continue running it until the player quits the game (or an error occurs).
- 3. Shut down the system and close safely.
We are currently looking at the portion of the code from the beginning of the function through the call to COM_InitArgv(). Directly above the function are several global variables that will come into play. They are all fairly self-explanatory, though there are some Windows-specific variables, like an HINSTANCE and HWND that are not all that important to dig into.
Inside the function itself, it calls a ‘quakeparams_t’ variable called parms. This will hold a variety of general parameters, including the command lines arguments passed to Quake itself. It is defined in the quakedef.h file.
It also fills a MEMORYSTATUS object. This object is used to pull in system information, such as the total amount of memory in the computer running it, virtual memory available, and so on. This version has been deprecated in favor of the MEMORYSTATUSEX object, and it may not pull in the right information in a 64-bit version of Windows, but modern computers have so much available memory that this won’t really matter that much.
The while loop goes through the command line arguments, moving them all into the global argv variable, and then copying them into the quakeparms_t variable as well. Note that Quake sets a hard limit of 50 command line variables that can be passed into the function – if you send it more than 50 variables it will simply ignore the extras.
It then calls COM_InitArgv(), and this is where we end our current lesson.
1. Do you understand the difference between WinMain() and main() functions? When do you use each one?
2. Look through the while loop and follow it. Imagine you are sending some command line variables to Quake – follow through the loop until you understand how exactly it is processing these variables.
void Sys_Error(char *error, …)
On line 219, you will see that if the program cannot determine the directory that Quake is located in, it will call a Sys_Error(), with the argument “Couldn’t determine current directory”. Let’s look at what this function does.
Our compile-ready version of Sys_Error has several sections highlighted out, as they include further function calls. Nonetheless, the current version still does much of what it needs to do. It begins by preparing an error message that will be sent to the console if you are running a dedicated server. In this situation, you can also see a while loop that will wait to get some feedback from the user in this case.
If it is not a dedicated server, the game switches to windowed mode using the commented out VID_SetDefaultMode() function, and then brings up an error box showing what happened. If it can’t restore to windowed mode it will send an additional error explaining that. It also brings in some extra shutdown functions that are currently ignored, and then calls the exit(1) function to shut down the game. Note that once Sys_Error() is called, the game will always close – this is an error that cannot be recovered from.
1. Manually put in a call to Sys_Error() function in WinMain(), so that you can see how it operates. Put in some of your own error functions and look at how the messages appear in the error box.
This function does some math to send back a 64-bit integer that determines how long the system has been currently running. The math behind the function is interesting, though there are some bit shifts that would take awhile to explain here.
int Q_strlen(char *str)
This is a simple function that manually counts the number of characters in the *str sent along, and returns the count. It does not count the ‘/0’ at the end of the string. Quite simple and straightforward.
1. How is Q_strlen used in WinMain()? Why is it used the way it’s used?
void COM_InitArgv(int argc, char **argv)
This function will copy the sent argc and argv arguments into the com_cmdline array, which is a duplicating effect added into Fitzquake. It also looks for a few particular arguments (-safe, -rogue, -hipnotic, -quoth) and sets variables for those. We will see those in the future.
1. This function uses pointers to copy over the argv commands. Make sure you can follow what’s happening – there will be more pointers in the future.
Much of this programming is Windows-specific and pointer work. It may not seem interesting at first glance, and yes, there is nothing to show for our efforts currently – the game just loads and shuts right back down. But in the end, if you want to be a professional game programmer, you will be well off to understand pointers, and at least be familiar with the Windows objects that are used here. If you program Windows desktop programs, you will see them again.