Quake Source Code Part 2

As we move into part 2 of our Quake Source Code project, we are still pretty deep inside Windows programming. All video games, even games that are 15 years old, have substantial initialization to go through when they start up, and Quake is no exception. You may find this pretty dull material, and to be honest, much of it is done differently in Windows 7, but there are also some memory management data structures pulled up that are useful to look at too.

And you can always just browse through the code and move onto the next lesson of course.

Here are the current files for uploading:

requakeStep2 compile
requakeStep2 CLOC

This file has a total of 4881 lines of code in it, up from 4542 last time, making our source code 11.4% complete. This 339 line increase will add 15 functions to the overall project, re-include some of the WinMain() code, and add numerous header declarations and data structures. Some of the functions are important initializers, and some are simple helper functions. Open the solution and the 7 files added to it, and let’s look through what we’ve added.

WinMain() revisited

The last project’s version of WinMain() ended with a call to COM_InitArgv(), which turned the command line arguments into global variables that any piece of code could read. We have extended it a little further now.

The first new thing WinMain() does is to use the new function COM_CheckParm() to see if the -dedicated argument was called. This means that the player wants to use this instance of Quake as a dedicated server for a multiplayer game. After this the line ‘#if 1′ appears. In Fitzquake, a small box that says ‘Starting Quake’ will pop up while the game is loading. By changing this line to ‘#if 0′ you will comment out the following lines of code to prevent that box from appearing. This is all Windows-heavy code, so we won’t dive too deeply into it.

After this, Quake attempts to use malloc() to reserve a chunk of memory for the game to run in. The original game wanted between 8 and 16 MB worth of memory, though Fitzquake was changed to set those limits to 8.5 MB and 32 MB. In modern computers this really isn’t an issue – it takes a truly archaic machine to not have 8 MB of free memory available, so virtually any modern computer will end up with a 32 MB block reserved. Alternatively, you can use the -heapsize argument to ask it to set a specific size. If it cannot obtain the required amount of memory, it uses Sys_Error() to shut the game down.

It then uses another new function, Sys_PageIn(), to access all the requested memory and allocate it into virtual memory. I’m a little fuzzy here, but I believe this would prevent the lag of having to do this while the game is running.

If this instance of Quake is a dedicated server, we have some other initializing to do. We look for a few other parameters and then call InitConProc().

The final thing our current version of WinMain() does is call Sys_Init(), where lots more initializing gets done.

int COM_CheckParm(char *parm)

This function is called to see if a command line argument was sent to the game. If you were to run the game at the prompt by typing in “Quake -windowed”, then calling COM_CheckParm(“-windowed”) would return 2, the position in the arg line of that parameter. Some arguments expect numbers after the argument, so you may have “Quake -maxPlayers 8″, in which case the game searches for “-maxplayers” and then pulls out the following argument from the arg list.

If the argument is not in the list, then the function returns 0.

void Sys_PageIn(void *ptr, int size)

This function is called by WinMain(), and may look a little confusing at first. This function is actually taking the chunk of memory that was just allocated and reading it all into virtual memory. From the notes that John Carmack left, it appears that this was a way to work around Windows 95′s memory management algorithms and to help prevent slowdown during the game due to memory that needs to be read from the disk instead of memory. Chances are this algorithm does not really offer much benefit in modern computers, but it’s an intriguing memory management hack that was likely effective in 1996.

void Sys_Init(void)

This function begins by calling some Windows functions that will set up the timer for the game. This will allow us to maintain a steady framerate. It then calls Sys_InitFloatTime() which will prepare the timer to actually be used. It then looks for the version of Windows you’re using. Since Fitzquake is based on WinQuake, this game requires Windows95 or higher. If you are running DOS or Win 3.1, it will Sys_Error() out of the game. Once it has determined that Windows is up to date, it returns.

void Sys_InitFloatTime(void)

This function prepares the system to use Sys_FloatTime() to keep track of how much time has passed. This will be the seed for the internal clock of our game, which balances everything from frame speed to input to… well, everything. The only thing it does differently from Sys_FloatTime() is that it checks for the ‘-starttime’ line argument to see if the player manually set the time that the game began playing. To be honest, this was probably for debugging purposes, as there’s no compelling reason for a player to want to set this on their own. After this is called, the game uses Sys_FloatTime() to return the current clock time.

void InitConProc(HANDLE hFile, HANDLE heventParent, HANDLE heventChild)

If you add the ‘-dedicated’ argument when running Quake, the game will call this function. WinMain() will search for a few other args, and you can use these args to get Quake to run a separate thread for QHost, but if you do not add -HFILE, -HPARENT, and -HCHILD arguments to Quake (along with numbers after them) this will not be done. It will simply create a console window and return.

DWORD RequestProc (DWORD dwNichts)

This is a function called by the thread that may be created in InitConProc(). It is Windows-heavy, and something that I’m not really digging into right now, so we’re going to pass on it right now.

void DeinitConProc(void)

This function is called when Sys_Error() is called. All is does is close down the thread that may have been created.

int VID_ForceUnlockedAndReturnState(void)

This function, along with the next few functions, is called in Sys_Error(), but it’s actually empty in the Fitzquake code. Perhaps this was something that was meant to be inserted that never was? Nonetheless, it has been added back into Sys_Error().

void VID_SetDefaultMode(void)

Another Sys_Error() function. The only thing this one does is closes down the mouse I/O. You may note that we currently do not initialize the mouse at all. So it effectively does nothing right now, but now that the code is in place, once we actually start using the mouse, we know it will shut down okay.

void Host_Shutdown(void)

Yet another function called by Sys_Error(). This is the function that shuts down the player’s game – its counterpart is Host_Init(), which we will see in the next lesson. As a result, it calls a number of other functions that shut down modules that we have not defined yet. Again, it’s good to have this in place, and we’ll revisit again in the future.

char *Sys_ConsoleInput(void)

The final Sys_Error() function added back in. This function returns immediately if you are not running a dedicated server. It processes the commands typed into the dedicated server console through some fancy char* work.

int Q_strlen (char *str)
int Q_strcmp (char *s1, char *s2)
int Q_atoi (char *str)
float Q_atof (char *str)

These functions are equivalent to the C standard functions strlen(), strcmp(), atoi() and atof(). I’m not sure why John Carmack decided to write his own versions, but he did, and so we’ll be using them for this program.

End Result

Like the last post, we are still heavy in Windows programming. We now get an initialization box when we begin the game, so there is an actual graphic result to running it now. We are now actually reserving the memory that the game will use to run, which is also great. But we have some shutdown functions defined before the initialization functions, which is odd. In the next post we will get into the Host_Init() function, which is a list of initialization functions for the various modules and subsystems in the game. It may take a few posts before we see any graphics, but as it stands we are slowly working our way through the foundation we need to get there. Video games are complex beasts, as this series is going to show.

No Comments