内容简介:The original source code forEverything is licensed under theI decided to take a peak at what goes on inside this game engine. I'm not going to spend my time reviewing every line of code, but it should at least be interesting to find out what game developm
The original source code for Command & Conquer as well as Command & Conqueror: Red Alert have been released by Electronic Arts. You can grab it here on GitHub .
Everything is licensed under the GPL v3 and the source code contains all the original comments. The original changelog from whatever VSC was used is not present. It looks like everything was just put into Git very recently.
I decided to take a peak at what goes on inside this game engine. I'm not going to spend my time reviewing every line of code, but it should at least be interesting to find out what game development in C++ was like in the early 1990s.
I'm looking only at the source code for "Command & Conquer: Red Alert" as it looks like it is a fork of the original. You can find it under the REDALERT
directory.
Statistics
- 290 C++ Header files
- 296 C++ implementation files
- 14 assembler files containing x86 assembler instructions
- 222090 lines of C++ code
I got the lines of code figure by counting up the non empty lines then subtracting out lines that were obviously just comments.
Almost all file names are uppercase only.
There is also a "RedAlert.vcxproj" file, so presumably this is ready to build in newer versions of visual studio. I did not confirm this.
Is this everything?
First off, there are no assets at all. Not even a test set. So you would need to somehow legally acquire those, for example you might buy the game from EA. Until you do that, even if you compile the code you can't really prove it works.
The code is littered with #ifdef WIN32
, in fact there are at least 430 occurences. There is a folder WIN32LIB
that contains some platform
specific implementations for Microsoft Windows. There are no other platforms implemented. The compiler directives for #ifdef WIN32
don't have an #else
anywhere I saw for other platforms. So I'm not entirely sure what the point was, but DOS was a supported platform. So maybe all the checks
allow a build of the game that works on DOS but not Windows.
I thought I found two files LCW.CPP
and LCWUNCMP.CPP
which
reference the playstation platform indirectly
// From LCW.CPP * Project Name : WESTWOOD LIBRARY (PSX) *
This file implements the LCW compression algorithm.
But what the "PSX" actually refers to is the POSIX
standard through a less than common acronym. There is even another implementation of the same compression function LCW_Comp
from LCW.CPP
in LCWCOMP.ASM
. I think the C++ implementation must have just been used
on some platforms where the assembly code wouldn't work.
So I guess they didn't release the Playstation port. I don't even know if the Playstation port was done by Westwood or if it was just subcontracted out.
Interestingly, LZO & LZW compression algorithms are also implemented. So I guess Westwood studios strategy for choosing which technology was "all of the above".
LCW should have been sufficient to dodge the Unisys patent claims on LZW, but maybe the LZW code just stuck around. The only usage of the
LZW code is in LZWPIPE.CPP
which defines the LZWPipe
class. The only usage of that code is commented out
// From SAVELOAD.CPP:423 LZOPipe pipe(LZOPipe::COMPRESS, SAVE_BLOCK_SIZE); // LZWPipe pipe(LZWPipe::COMPRESS, SAVE_BLOCK_SIZE); // LCWPipe pipe(LCWPipe::COMPRESS, SAVE_BLOCK_SIZE);
It looks like a few different algorithms were tried in this case and LZO was settled on. So LZW appears to be available, but not used.
Unisys patent claim against LZW expired a long time ago, so there is no longer any concern over this.
Weird headers
A header like this present in LZW.H and other files
// From the top of LZW.H /* $Header: /CounterStrike/LZW.H 1 3/03/97 10:25a Joe_bostic $ */
The "CounterStrike" here refers to "Command And Conquer: Counterstrike" which is an expansion pack. There are 215 occurences of #ifdef FIXIT_CSII
that
somehow changes things for the expansion pack. The full explanation comes from "awj" on 9-28-1998 in a comment
// From DEFINES.H #define FIXIT_CSII // Adds Aftermath CounterStrike II units // ajw 9/28/98 - Note about FIXIT_CSII. Changes seem to have been made for Aftermath ("Counterstrike II") that: a) were // bug fixes that should never be rolled back, b) change the nature of the game, at least in multi-player. This meant // that the "Red Alert" executable ( == Counterstrike executable ) could no longer be built. Apparently, at the time, // this was justified, as it was believed that no further patches to the RA executable would ever be necessary. // Given that Denzil's DVD changes and my WOLAPI integration are essentially a patch, we've got a problem. // We've decided to level the field and make sure every who gets or patches to the new version of Red Alert, CS, AM, (and // their DVD equivalent(s)) will have the same executable. So we're assuming that all of the FIXIT_CSII changes are // permanent (as, in fact, all prior FIXIT_'s are - makes me wonder why the old non-compiling code has to hang around // forever), and fixing the code so that the assumption "this is an Aftermath game" is no longer hard-coded, but can // change at runtime. (Which is what should have been done when Aftermath was created.) // <This goes for the following three defines as well.> #define FIXIT_CARRIER // Adds Aftermath aircraft carrier #define FIXIT_PHASETRANSPORT // Adds Aftermath cloaking APC // ajw - Discovered that engineer changing fields were specifically left out of aftrmath.ini, thus this has no effect. // Engineer changes (and other game rule changes) are in mplayer.ini, which was loaded before aftermath-only mplayer games. #define FIXIT_ENGINEER // Adds Engineer rules.ini overrides
Missing source code
There is also the question of WOLAPI.dll
which seems to mean "Westwood Online API". This was used for online matchmaking. The author of
Command & Conquer: Red Alert apparently disliked this library enough to write a complete wrapper around it, it is in WOLAPIOB.H
. You can probably
just go find the original wolsetup.exe
file and extract the DLL from it. But it also likely isn't very useful.
Funnily enough, it appears that WOLAPI.dll
was actually reverse engineered over 9 years ago
This is not a full source code release , but should be enough for people to understand the internal game mechanics. EA stated the reason for their release was to help modders, so this probably accomplishes that. With substantial work a standalone game could be built.
How does the game startup?
I decided to start by looking at how the game starts up. Startup code usually gives you a good idea of how a game engine communicates with the operating system. This is presented roughly in the order that it actually is in the code, but not exactly. Anything not interesting is skipped.
The C++ main
function.
The main
function is defined in STARTUP.CPP
. The game appears to have recently been rebuilt as a DLL, presumably for the remastered
version that is being released. So the first thing it does is assigns RunningAsDLL = true;
The main function checks #ifdef MPEGMOVIE
as well as #ifdef MCIMPEG
all over the place. In DEFINES.H
this is commented out entirely
// Define DVD to turn on RADVD additions/changes - Denzil #ifdef DVD //#define INTERNET_OFF //#define MPEGMOVIE //PG //#define MCIMPEG #endif
So presumably the functionality to play MPEG cinematics is missing from the game.
The first thing that happens is it checks for another copy of the game runing and refuses to start. This seems to have been disabled with
a #if (0)
. There are many sections of code disabled in this manner, each one featuring a //PG
. One of the committers in Git is PG-SteveT
so I guess the same person who put this project in Git was responsible for porting this to be a DLL rather than a standalone
executable. There are many more sections like this in the main
function, I am referring to them just as disabled from here forward.
Next up is checking for free memory. The first check is just allocate 13 megabytes of memory and if that works free it. There is another check that is disabled that tries to allocate 13 megabytes of memory in a loop. Each iteration of the loop reduces the requested allocation by 1 kilobyte until it passes. I think it is safe to assume modern computers running Command & Conquer will have the needed 13 megabytes of memory, so it is sensible that this is disabled.
The next check is to see if the command line contains f:\\projects\\c&c0
or F:\\PROJECTS\C&C0
and refuse to start if that is the case.
If it is, a dialog box saying "Playing off of the network is not allowed." pops up and the game exits. A quick inspection of some header
files lead me to find this
"/* $Header: F:\projects\c&c0\vcs\code\wwalloc.h_v 4.9 07 May 1996 17:14:00 JOE_BOSTIC $ */" in WWALLOC.H.
Since "vcs" is in the path it seems like an early version control system was used, that acted as a mounted drive in Microsoft Windows. The last time I used IBM Rational ClearCase it still works like this. This probably was done to prevent people from running the game from the network share and constantly overwriting each other's save files.
The game implements its own command line parsing logic, implemented inline in the main function
// From STARTUP.CPP /* ** Get pointers to command line arguments just like if we were in DOS ** ** The command line we get is cr/zero? terminated. ** */ command_scan=0; do { /* ** Scan for non-space character on command line */ do { command_char = *( command_line+command_scan++ ); } while ( command_char==' ' ); if ( command_char!=0 && command_char != 13 ) { argv[argc++]=command_line+command_scan-1; /* ** Scan for space character on command line */ bool in_quotes = false; do { command_char = *( command_line+command_scan++ ); if (command_char == '"') { in_quotes = !in_quotes; } } while ( (in_quotes || command_char!=' ') && command_char != 0 && command_char!=13 ); *( command_line+command_scan-1 ) = 0; } } while ( command_char != 0 && command_char != 13 && argc<20 );
I didn't really bother proving this works, but it looks like it scans for things being in quotes on the command line and then
tries to treat them as groups if they are quoted using "
. I guess the Windows command line couldn't handle this so it was done in the
game directly. I actually don't know if cmd.exe
implements this quoting logic even today.
Main then saves off the original working directory and drive the game was launched from, before changing to the directory with the
executable. Interestingly this seems to default to A:
which would usually be a floppy drive on Windows. This code path is disabled
entirely now.
Next up is the first #ifdef WOLAPI_INTEGRATION
. This isn't defined and the source code for the Westwood Online API is not available.
If it were defined, the game would try and find a file named wolsetup.exe
and run it, before checking for a Windows registry key
indicating that setup is complete. A rather interesting comment shows up here
// From STARTUP.CPP // I've been having problems getting the patch to delete "conquer.eng", which is present in the game // directory for 1.08, but which must NOT be present for this version (Aftermath mix files provide the // string overrides that the 1.08 separate conquer.eng did before Aftermath). // Delete conquer.eng if it's found. if( FindFirstFile( "conquer.eng", &wfd ) != INVALID_HANDLE_VALUE ) DeleteFile( "conquer.eng" );
Apparently the developers after version 1.08 of the game really needed to not have "conquer.eng" be present. So they just try and delete it every time the game starts up. Why this is packed in with the Westwood Online code, I don't know.
Now we move on to a check for #if(TEN)
which is not defined. If it were defined, the game would setup for support for the Total Entertainment Network
.
Right after that check there is a check for #if(MPATH)
which is also not defined. It if were then the game would
have support for the MPlayer
.
Both were decently popular online gaming platforms in the last half of the 1990s. My guess is there was a custom build for MPlayer & TEN support. Neither exists today. I discuss these more later in this article.
Now the game starts a Windows timer, sleeps for 1000 milliseconds and makes sure the system clock has advances. If
this fails it exits the game. I guess the implementation of sleep
on some platforms was sufficiently faulty to warrant
such a check during startup. This code has now been disabled.
// From STARTUP.CPP #if (0)//PG int time_test = WindowsTimer->Get_System_Tick_Count(); Sleep (1000); if (WindowsTimer->Get_System_Tick_Count() == time_test){ MessageBox(0, TEXT_ERROR_TIMER, TEXT_SHORT_TITLE, MB_OK|MB_ICONSTOP); return(EXIT_FAILURE); } #endif
Then the game checks for free disk space and if it doesn't have enough it exits. This code has now been disabled.
It's now onwards to loading the configuration generated from the ccsetup
program that comae with the original game.
This doesn't do anything too interesting, but I did notice this
: cpp // From STARTUP.CPP if (!Debug_Quiet) { Audio_Init(NewConfig.DigitCard, NewConfig.Port, NewConfig.IRQ, NewConfig.DMA, PLAYBACK_RATE_NORMAL, // (NewConfig.Speed) ? PLAYBACK_RATE_SLOW : PLAYBACK_RATE_NORMAL, NewConfig.BitsPerSample, // 4, (Get_CPU() < 5) ? 3 : 5, // (NewConfig.Speed) ? 3 : 5, NewConfig.Reverse); SoundOn = true; } else { Audio_Init(0, -1, -1, -1, PLAYBACK_RATE_NORMAL, 8, 5, false); }
A "DebugQuiet" configuration was added that appears to select an audio output that does nothing. I suppose it would be quite annoying if you were testing the game to be continually subjected to the noise.
At this point the game is ready to configure the display resolution. This code is exceptionally interesting. The first step is to check for a screen height of 400, which means a mode of 640 x 400 has been selected. If this can't be set it falls back to 640 x 480. Any other screen resolution is uset directly. What is so special about a mode of 640 x 400?
To actually do rendering the game needs graphics memory access. A screen width of 320 results in a special code path running to initialize the display buffers. My guess is anyone running on a video mode of 320x240 (which is still VGA) is running on such old hardware they don't bother trying to use hardware buffers. Any other screen height results in an attempt to allocate hardware buffers for the visible memory page and a hidden memory page. I suspect double buffering of the graphics is used, hence two buffers. For the visible memory page it is mandatory that hardware memory is available. There is some interesting logic around this allocation
// From STARTUP.CPP VisiblePage.Init( ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)(GBC_VISIBLE | GBC_VIDEOMEM)); /* ** Check that we really got a video memory page. Failure is fatal. */ memset (&surface_capabilities, 0, sizeof(surface_capabilities)); VisiblePage.Get_DD_Surface()->GetCaps(&surface_capabilities); if (surface_capabilities.dwCaps & DDSCAPS_SYSTEMMEMORY) { /* ** Aaaarrgghh! */ WWDebugString(TEXT_DDRAW_ERROR);WWDebugString("\n"); MessageBox(MainWindow, TEXT_DDRAW_ERROR, TEXT_SHORT_TITLE, MB_ICONEXCLAMATION|MB_OK); if (WindowsTimer) delete WindowsTimer; return (EXIT_FAILURE); }
I can only imagine that the "Aaaarrgghh!" comment was added after a long session of debugging performance issues on some machines. At some point someone realized that the DirectDraw API (part of DirectX) could return a system memory page even when a hardware video page is asked for.
The hidden page is a bit more lenient, but has the logic and even better comments
// From STARTUP.CPP /* ** If we have enough left then put the hidpage in video memory unless... ** ** If there is no blitter then we will get better performance with a system ** memory hidpage ** ** Use a system memory page if the user has specified it via the ccsetup program. */ unsigned video_memory = Get_Free_Video_Memory(); unsigned video_capabilities = Get_Video_Hardware_Capabilities(); if (video_memory < (unsigned int)(ScreenWidth*ScreenHeight) || (! (video_capabilities & VIDEO_BLITTER)) || (video_capabilities & VIDEO_NO_HARDWARE_ASSIST) || !VideoBackBufferAllowed) { HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); } else { //HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)GBC_VIDEOMEM); /* ** Make sure we really got a video memory hid page. If we didnt then things ** will run very slowly. */ memset (&surface_capabilities, 0, sizeof(surface_capabilities)); HiddenPage.Get_DD_Surface()->GetCaps(&surface_capabilities); if (surface_capabilities.dwCaps & DDSCAPS_SYSTEMMEMORY) { /* ** Oh dear, big trub. This must be an IBM Aptiva or something similarly cruddy. ** We must redo the Hidden Page as system memory. */ AllSurfaces.Remove_DD_Surface(HiddenPage.Get_DD_Surface()); // Remove the old surface from the AllSurfaces list HiddenPage.Get_DD_Surface()->Release(); HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); } else { VisiblePage.Attach_DD_Surface(&HiddenPage); } }
The same "double check" applies to this page as well, but can fallback to system memory.
The comment specifically calls out the IBM Aptiva
as a source of these issues. I suspect the IBM Aptiva must have
been the butt of many jokes during develpoment because DEFINES.H
even has a line for #define FIXIT_APTIVA_MODEM
which does in fact use special
logic for dealing with the IBM Aptiva's modem.
At this point the game now configures the ScreenHeight to exactly 3072. A graphics buffer is attached to the visible & hidden pages it with dimensions of 3072 x 3072. As far as I can tell almost all the above code around getting hardware memory is in fact disabled by the use of #if
statements. It's such a mess it is hard to tell what is enabled and what is not. But all the press releases about the remaster mention "4K" support. So maybe the real resolution of this "4K" remaster is in fact 3072 x 3072.
As recently as 2019, some changes were made with comments indicating the date and time. Someone with initials JAS added a check to see if they were running from the editor (presumably the level editor) and avoids taking over the mosue if that is the case. The remaster was announce in Novement 2018, so this timeframe makes sense for various changes need to implement & test 4K support with new asset files.
There is all kinds of now disabled logic about forcing the intro cinematic on the first run through. It's almost bipolar, unable to make its mind up as to whether or not the user really needs to see this cinematic. At some point this was commented out, probably during the remaster development. I imagine developers and testers got very tired of watching the intro cinematic very quick, so I suspect it would have been removed during the original devlopment as well.
The game is now ready to play!
It then runs Main_Game
(from CONQUER.CPP
) which is the actual game loop. The start date in a comment in this file is "April 3, 1991", so the original Command & Conquer had 4 years of development before release in 1995!
Once the game is complete, the Main_Game
function can return. There is a bunch of cleanup code, but before we get there we have this check
// From STARTUP.CPP if (RunningAsDLL) { //PG return (EXIT_SUCCESS); }
So presumably the DLL leaves the graphics in a weird state and probably leaks memory all over the place.
Other investigations
I decided to spend some time looking at other things I found interesting, based off the filenames.
Windows 95 Stack traces.
The file W95TRACE.CPP
contains the comment
/ * Implementation of Win95 tracing facility to mimic that of NT". */
This seems to suggest development was done on Windows NT, but testing was probably done on Windows 95 to make sure it worked for the intended audience. This would make sense as NT would be available as early as July 1993 to developers working on the game. I suspect the Windows 95 stack traces are woefully unhelpful compared to what Windows NT could provide.
Watcom compiler
There is also WATCOM.H
that defines lots of "#pragma", which are all obviously specific to the then popular Watcom compiler.
This is sort of interesting, because the Watcom compiler first release was 1993 for C++ support. There are other development notes indicating this project goes back to 1991. Did it start off as a C project and move to C++? Or did they have another C++ compiler available?
There is also this gem from the same file, I guess some compiler writers could not be bothered to implement true & false at this time
// From WATCOM.H // Fix deficiency in Watcom so that true/false will be defined. #ifndef __BORLANDC__ #ifndef TRUE_FALSE_DEFINED #define TRUE_FALSE_DEFINED enum {false=0,true=1}; typedef int bool; #endif #endif
Encryption
Blowfish encryption is implemented in BLOWFISH.CPP, which presumably is the well known Blowfish cipher . The only real use I can think for for this is to obfuscate network traffic.
Entity system
The CCPtr
class in CCPTR.H is a pointer like type that uses an ID
as an offset into a memory heap. This is pretty common, because it makes saving the state of the
game to disk easier if all objects are tracked in one place. This functions as a very simple way to have an entity system. If you're curious about what the
advantages of an entity system are in a video game, you can read more here
.
Vectors
The VECTOR.H
class defines a template class vaguely similar to std::vector. Presumably this was developed too early to take advantage of STL.
Dipthongs
So a dipthong
is a combination of two adjacent vowel sounds within the same syllable. What does this
have to do with video game code? Nothing honestly. The strangest file is WIN32LIHB/DIPTHONG.H
. The actual description given of this is
found in the header
// From WIN32LIB/DIPTHONG.CPP * DIGRAM or DIATOMIC encoding is the correct term for this method. * * This is a fixed dictionary digram encoding optimized for English text. *
This is bascially a simple compression algorithm that assumes you're compressing text and does some subsequence replacement.
In the code we find the magic number 4567
used when making some defines
// From WIN32LIB/DIPTHONG.CPP #define TXT_GUEST 4567+3 #define TXT_LOGIN 4567+4 #define TXT_LOGIN_TO_INTERNET 4567+5 #define TXT_YOUR_HANDLE 4567+6 #define TXT_YOUR_PASSWORD 4567+7 #define TXT_INTERNET_HOST 4567+8 #define TXT_INTERNET_JOIN 4567+9 #define TXT_INTERNET_GAME_TYPE 4567+10 #define TXT_JOIN_INTERNET_GAME 4567+11 #define TXT_ENTER_IP_ADDRESS 4567+12 #define TXT_WINSOCK_CONNECTING 4567+13 #define TXT_WINSOCK_NOT_CONNECTING 4567+14 #define TXT_WINSOCK_UNABLE_TO_CONNECT_TO_SERVER 4567+15 #define TXT_WINSOCK_CONTACTING_SERVER 4567+16 #define TXT_WINSOCK_SERVER_ADDRESS_LOOKUP_FAILED 4567+17 #define TXT_WINSOCK_UNABLE_TO_ACCEPT_CLIENT 4567+18 #define TXT_WINSOCK_UNABLE_TO_CONNECT 4567+19 #define TXT_WINSOCK_CONNECTION_LOST 4567+20 #define TXT_WINSOCK_RESOLVING_HOST_ADDRESS 4567+21
The following code runs as part of decompression
// From WIN32LIB/DIPTHONG.CPP if (string >= 4567) return (InternetTxt[string-4567]); ptr = (unsigned short int const *)data; return (((char*)data) + ptr[string]);
The array "InternetTxt" is just a bunch of predefined strings. It appears anything equal to or above "4567" is considered to be a predefined string. My guess is this constant was chosen by the developer pressing the keys 4-5-6-7 on the digits row on their keyboard. When I need to generate a random integer to use as a constant, I prefer this method .
The Entertainment Network & MPlayer
As I mentioned up above, there appear to be custom builds for supporting The Entertainment Network & MPlayer. These were online multiplayer services at the time.
Each service got its own custom initialization & teardown code.
The Entertainment Network (TEN) files
- CCTEN.CPP
- TENMGR.H
The following functions are used but have no implementation in the source code
- tenArSendToPlayer
- tenArExitArena
- tenArIdleArena
- tenArReturnGameOptions
- tenArReturnPlayerOptions
- tenArSendToOtherPlayers
- tenArSendToPlayer
- tenArSetAlertMessageRoutine
- tenArSetIncomingPacketRoutine
- tenArSetOption
- tenArSetPlayerEnteredRoutine
- tenArSetPlayerState
- tenArSetPregameHookRoutine
- tenArUnreliableSendToOtherPlayers
- tenArUnreliableSendToPlayer
MPlayer (MPATH) files
- CCMPATH.CPP
- MPMGRW.H
- MPMGRD.H
The following functions are used but have no implementation in the source code
- MGenMoveTo
- MGenGetMasterNode
- MGenFlushNodes
- MGenMCount
- MGenSanityCheck
- MGenGetNode
- MGenGetQueueCtr
I can't find any details on the missing functions from the TEN code.
The "MPATH" code calls a bunch of "MGen" functions which are also missing. However, you can just go look them up in the Quake Source code . Quake was also available on both of these networks. The TEN code isn't present in the public Quake source however. I am guessing that MPlayer integration was done by just sharing a standard library with the development studio and having them integrate it.
My guess is EA was cautious and didn't feel they owned this code, whereas id software may have simply not cared at the time that Quake was released.
Of course, no one actually needs this code. It's just the last thing that remains of what was a staple of late 1990s gaming services.
Translation & Localization
It's always common to see games ported to other languages, so they can appeal to a wider market than the English speaking world.
Interesting, the localization was done at least in part by using #ifdef GERMAN
statements sprinkled around as well as being checked
in LANGUAGE.H
before defining things like TEXT_SETUP_FIRST
.
The check #ifdef GERMAN
shows up 28 times, but #ifdef FRENCH
shows up 37 times. Maybe the French translation was just a bit more thorough.
Of course DEFINES.H
has the best translation implementation
// From DEFINES.H //#define SPAIN 1 (never used)
I guess they just never got around to the Spanish translation.
Dongle protection
The hardware dongle was a common method of DRM for all sorts of software at some point in the past. Basically you have this obscure & obfuscated piece of hardware you attach to your PC. The software issues some sort of "challenge" to it and refuses to start if it doesn't get an answer it likes.
I don't think Command & Conquer every shipped with this as a thing, but DEFINES.H
does have a commented out line or two about it
// From DEFINES.H /********************************************************************** ** ColinM ** Set this to enable dongle protection */ //#define DONGLE
Virgin Interactive
Westwood studios developed Command & Conquer but was owned by Virgin Interactive. There is this uniquely named check
// From DEFINES.H /********************************************************************** ** If this is defined, the special Virgin limited cheat keys ** are enabled. This allows the "cheat" parameter and then only ** allows the ALT-W to win the mission. */ #ifdef PLAYTEST_VERSION #define VIRGIN_CHEAT_KEYS #endif
// From INIT.CPP #ifdef VIRGIN_CHEAT_KEYS case PARM_PLAYTEST: Debug_Playtest = true; break; #endif
I guess that before release, Virgin Interactive got a special built that allowed them do some play testing on the product.
The end
That's it. I didn't look at everything, just what I found interesting and had time to. I learned a little bit about early 1990s video game development and maybe I can put some of that knowledge to use in the present.
Maybe in the future I will visit this again. I was really looking forward seeing how the the Playstation port of the game compared, but I am guessing EA cannot actually release such a thing even if they have the source code still.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。