How To Compile Without Microsoft Visual C (Part 1)

From ioquake3 wiki
Jump to: navigation, search

Article 4 - How to Compile Without Microsoft Visual C++ (Part 1)

By [hyopthermia]

This article is aimed at the Quake3 Arena mod developer who can't or won't work with Microsoft Visual C++. In addition it helps lift the restriction that all coders working on a project use the same tools in the same environment.

You'll need the Quake3 Source and the tools that go with it to get started.

Summarized here is the experience I had in building the Q3Source for the Borland compilers. Hopefully you can use this to create the tools required to develop Q3Source using the compiler you're familiar with.

I'd encouraged you to base your compiler solution in a new directory under \quake3\source. Examples are provided based on the work I did. There's no substitute for real world experience. Code3Arena will act as a focus for providing compiler solutions for building Q3Source. Mod makers can then choose the platform and compiler they want to work on.

Contact me if you do make a successful port to another compiler. If you have any questions about porting to another compiler then I'll try to help. No binaries or attachments over 50K without my permission please!

Sit back, kick off your shoes, and try not to think of cheese!

1. Background

The Quake3 Source released by id Software builds part of the game code, allowing dedicated enthusiasts to add to and improve Quake 3 for the gaming community at large. As the game already ships on several platforms it's clear that the Q3Source needed to be as platform independent as possible. It was written using portable ANSI C and compiled to a bytecode that runs on any machine with a Q3 executable.

Mod makers can finally develop game enhancements: on a single platform, for multiple platforms.

One beneficial side effect of using portable ANSI C is that a large number of programmers are already familiar with C as a programming language. The other main benefit is the ability to build binaries that only work on the one platform for development and debugging.

2. The Objectives

The objective can be split into three parts:

  1. Building the bytecode for use with Quake3 (and redistribution)
  2. Building binaries that can be tested and debugged on your system
  3. Releasing your work for others to use

When you release your work for others to use it should make the minimal number of changes to the Q3Source installation. Preferably you should provide your own batch files or scripts, making sure they don't overwrite the ones supplied with Q3Source. Id Software might release an updated source, overwriting your files. Ideally a mod developer should only need to re-apply your necessary changes to the Q3Source codebase.

Read 'Distribute your project' in the Second part of this article for ideas on how to do this so you can start as you mean to go on.

In the first two cases you'll be using the header files supplied with your compiler. The main issue for the bytecode is making your headers look like ANSI C. See '4. Getting started on the bytecode' for more information.

Building binaries for your system needs an understanding of how to modify portable code in a way that keeps it portable. In other words, another compiler should be able to use the Q3Source code you've modifed without running into problems of its own. See 'Compiling the binaries' in the Second part of this article for details.

3. Getting started on the bytecode

The bytecode that runs on the Quake3 Virtual Machine (QVM) is platform independent. It's compiled using lcc.exe, a tool supplied by id Software, and will use the header files from your compiler. Each of the compiled files is then assembled and linked using q3asm.exe.

There are three separate QVM files you'll be compiling:

  • qagame.qvm Contains the code needed to run a game server. In single player this also controls the bot AI.
  • cgame.qvm Handles the events and screen display on your local (client) machine.
  • ui.qvm Provides the User Interface and menu front end to the single player game.

To get the bytecode to compile you'll need to work out how to make your header files appear as platform independent ANSI C.

4. Automating compilation using scripts

Provided as part of Q3Source are 4 batch files that run from the DOS prompt. Three of the files are concerned with building each of bytecode modules qagame, cgame,and ui, called game.bat, cgame.bat and ui.bat. Each calls the fourth batch file compile.bat with the location of a source file needed to build that QVM module.

Once compiled, the last job of each script is to assemble and link the files to make the distributable. q3asm.exe uses a response file for each module: game.q3asm, cgame.q3asm and ui.q3asm.

Copy these files to your compiler directory under Quake3\source. Noticing that they actually do their work in a subdirectory called vm, modify them in the following way:

  • Adapt the batch file to work using the script language on your system, making sure that you still use the compile script to call lcc.exe.
  • change the relative paths to each of the source files
  • change the relative paths to the executables lcc.exe and q3asm.exe, or add them to your executable path (document this!)
  • change the relative paths to ..\cgame, ..\game, and ..\ui in compile.
  • in each of the .q3asm files: modify the path to cg_syscalls, ui_syscalls or g_syscalls only, so it uses the right .asm file in the Q3Source subdirectories cgame, ui, and game repectively.
  • If you run each of there files they should now *try* to compile the source, bombing out with an error about not finding some header files.

5. Understanding your header files

From now on the changes you need to make are in the compile script only.

The first modification to compile is to tell it where your header files are. Make sure the following argument is passed to lcc.exe:

-I<path to header files>

where <path to header files> is an absolute path correct for your system.

Now that your header files can be found you'll start running into platform and compiler specific issues. Most of these should be solved by passing the equivalent of a #define xxxxxx to lcc.exe. You can do this by adding the argument -Dxxxxxx or -Dxxxxxx="" in the compile script. If your header files can be used on more than one compilation model then you need to work out a path through them that gives a "pure" ANSI C definition of all functions. One way to do this is find the header file that defines the compiler specific information, bypass it, and supply your own definitions to lcc.exe.

You may also have to define some compiler specific values to help control the route through the header files. Check that these aren't used in the Q3Source already, and if they are that they won't cause problems.

Example: Borland C++ uses header files that can build for executables or DLLs in Win32, Win16, and executables for DOS in 6 different memory models. I chose to force the Win32 executable path onto the Borland header files by defining __FLAT__. I had to avoid the Win32 references in Q3Source (we're not compiling for that platform!) so I didn't define WIN32, _WIN32 or __WIN32__. These variables are defined and used by either Borland or Microsoft tools.

I also defined the compiler specific value __BORLANDC__, further controlling the route through the headers. With these defined I started getting errors from lcc.exe about _RTLENTRY and similar constants, so I had to remove the header file that supplied these definitions <_defs.h> (defining ___DEFS_H did this as the header was protected from repeated inclusion by this value). A typical Borland definition looks like:

int _RTLENTRYF atoi(const char _FAR *__s);

and applying these definitions reduced it to:

int atoi(const char *__s);

6. Keeping the code portable (and how to make necessary changes)

Avoid making modifications to the Q3Source, unless you can absolutely have to. Try and make changes through the command line options in the compile script instead.

When you have make modifications to Q3Source, do so after you've determined that the use of -Dxxxxxx can't solve your problem. Make the changes so that they are controlled by a constant defined only by your compiler, make sure this constant is defined in the compile script as well. Make your necessary changes to files that already have a compiler specific component in them (game\q_shared.h for example). You should only need to touch a few header files.

If you need to edit a C source file, think again! Look at 'Expected errors', there should be no need to fix these.

If a route through the Q3Source header files is already available for your platform, but you're using a different compiler, then take advantage of it.

Document your changes and allow the recipient of your work to incorporate them, understanding the benefit themselves. Remember: you're helping people who are already exerienced C coders.

Example#1: Using the Borland header files there was a clash with the definition of random(). As the Q3Source definition needed to take precedence, the following code was inserted:

#if (defined __BORLANDC__ && defined random)
#undef random

and was added after game\q_shared.h line 424.

Notice that this also works when building binaries, as the same problem arises.

Example#2: The only other change I had to make to a source file was in a Borland header file. lcc.exe was generating an error while parsing a #error directive (even though it wasn't executed). The change made to the Borland header put the error message into quotes.

#error "Can't include both STDARG.H and VARARGS.H"

in <stdarg.h> line 20.

7. Expected errors

Despite the portablilty of the Q3Source, warnings are generated by the source. You might also get a few warnings from your header files. Work out why and decide whether any change is needed in your header files.


"Warning: Conversion of 'pointer to void'... compiler dependent"

Not a problem for the QVM, though it might be for your compiler.

You might also find that your bytecode modules are of a different size to those released with Quake3. This is probably caused by differences between how the header files use static data for their implementation.

Example: The Borland header files generate another warning:

limits.h:31 Character constant taken as not signed

Paradoxically this is warning about the method employed by Borland to find out if a char type is signed or unsigned. It can be ignored.

8. Testing the bytecode

Follow the instructions in Tutorial 3: Hello, Qworld! for modifying the source code to produce the "Slow rocket mod". Don't forget to change things back!

Try playing back the demos using "timedemo 1". This is not a performance test, but ensures that the same frames are drawn each time. Play a few games against bots by compiling the bytecode into a directory other than baseq3. With or without the slow rocket mod!

Go on! You've earned it!

9. Continuing the good work

With the compiled QVM's under your belt the next thing to look at is getting the code compiled to produce binaries. This is covered in the second part to the article, as well as some suggestions on how to organize your work if you want to distribute it.

« Article 3 - Getting the most from Quake3 C | Article 4 - How To Compile Without Microsoft Visual C++ (Part 1) | Article 5 - How To Compile Without Microsoft Visual C++ (Part 2) »