by Brent Orford
Project - X (c) 1999 Brent Orford.
This code is distributed freely as an example of a basic game shell using the Genesis 1.0 API.
To get the most up to date code for this tutorial go HERE.
This game shell has support for...
- Jumping
- Crouching
- Mouselook
- FPS independence
- KB Movement
- Gravity
- Wall sliding
***** UPDATE *****
The code in this tutorial has been updated. Also two new pages of info have been added as
follows:
What's New
Setting it up
Directory structure:
All files contained in the .zip should be unpacked to a directory of your choice (i.e. C:\ProjectX\).
D3DDrv.dll GBSPLib.dll Genesis.dll GlideDrv.dll SoftDrv.dll Softdrv2.dll should be copied from the Genesis3d directory
into the new directory (C:\ProjectX\).
A levels directory (C:\ProjectX\levels) should be created and the levels from the Genesis GTest levels directory
should be copied over. (genvs.3dt and genvs.bsp should be copied.)
A lib directory (C:\ProjectX\lib) should be created and genesis.lib genesisd.lib genesisi.lib should be copied
over from the Genesis3d\lib directory.
An include directory (C:\ProjectX\include) should also be created and the Genesis3d\include files should be copied
over.
Download the .zip file containing the code HERE.
Code:
ProjectX.h:
static HWND CreateMainWindow (HINSTANCE hInstance, char *AppName, int32 Width, int32 Height);
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
void MoveHead(void);
void ApplyGravity(void);
void LoadPrefs(int *CWidth, int *CHeight, char *OurDriver);
static geBoolean IsKeyDown(int KeyCode);
void Setup(void);
void Jump (void);
void Crouch (void);
void UnCrouch(void);
void Move (float speed);
int CWidth = 640;
int CHeight = 480;
geDriver_System *DrvSys = NULL;
geDriver *Driver = NULL;
geDriver_Mode *Mode = NULL;
char *modename = NULL,
*drvname = NULL;
char ourdriver = '(';
geWorld *World = NULL;
geCamera *Camera = NULL;
geRect Rect;
POINT pos;
geVFile *ActorFile, *MainFS, *Level = NULL;
HWND mainwindowhandle;
geEngine *Engine = NULL;
MSG Msg;
geXForm3d Xform;
geXForm3d ViewXForm;
geVec3d Angles;
geVec3d oldpos; //old and new pos are temporary values
geVec3d newpos;
geVec3d In;
geVec3d Mins; //this is for collision detect
geVec3d Maxs; //they set your minimum and maximum size.
GE_Collision Collision;
BOOL Result;
geBoolean Debuginfo;
float Bd;
typedef struct Player_Info_Struct
{
float CurrentHeight; //how tall we currently are
float NormalHeight; //how tall we normally are
int CurrentSpeed; //how fast we currently are
int NormalSpeed; //how fast we normally are
int TimeinAir;
float FallingSpeed;
} Player_Info;
Player_Info Player;
geXForm3d ActorXform;
geFloat ModelCounter = 0.0f;
geMotion *Motion = NULL;
geActor_Def *ActorDef;
geActor *Actor;
ProjectX.c:
#include <windows.h>
#include <stdio.h>
#include "include\Genesis.h"
#include "ProjectX.h"
/////////////////////////////////////////////////////////////////////////////////////
// ProjectX was written as an example of how to start a first person perspective
// game using Genesis 1.0. Use it however you want, if you have any problems with
// it that I should know about or you make improvements, I probably would like to
// know what they are so write me an e-mail. This code was based off the original
// 3rd person perspective Minapp, but is now 1st person, has mouselook as well as
// movement commands. FPS independence has been implemented. Gravity, Jumping and
// crouching have also been implemented.
/////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
int run, Height, Width, tick;
long LastTickCount = GetTickCount();
geBitmap *Black = NULL;
pos.x = 0;
pos.y = 0;
LoadPrefs(&CWidth, &CHeight, &ourdriver);
mainwindowhandle = CreateMainWindow(hInstance, "Project - X", 640, 480);
Engine = geEngine_Create(mainwindowhandle, "Project - X", ".");
geEngine_EnableFrameRateCounter(Engine, GE_FALSE); // Disable the frame rate counter.
if (ourdriver == '(') ShowCursor( FALSE ); // If DX Turn the cursor off!
if (!Engine) MessageBox(NULL,"No Engine", "Error", MB_OK);
DrvSys = geEngine_GetDriverSystem(Engine);
if (!DrvSys) MessageBox(NULL,"No DrvSys", "Error", MB_OK);
Driver = geDriver_SystemGetNextDriver(DrvSys, NULL);
if (!Driver) MessageBox(NULL,"No Driver","Error", MB_OK);
while(1) {
geDriver_GetName(Driver, &drvname);
if (drvname[0] == ourdriver) break; // check to see if it is the correct driver
Driver = geDriver_SystemGetNextDriver(DrvSys, Driver);
if (!Driver) _exit(-1);
}
Mode = geDriver_GetNextMode(Driver, NULL);
while(1) {
if (!Mode) MessageBox(NULL,"No Mode","Error", MB_OK);
geDriver_ModeGetWidthHeight(Mode, &Width, &Height);
if (Width == CWidth && Height == CHeight) break;
Mode = geDriver_GetNextMode(Driver, Mode);
}
if (!geEngine_SetDriverAndMode(Engine, Driver, Mode))
{
MessageBox(NULL,"Set Driver/Mode failed","Error", MB_OK);
_exit(-1);
}
geEngine_SetGamma(Engine, 2.0f);
Rect.Left = 0;
Rect.Right = CWidth - 1;
Rect.Top = 0;
Rect.Bottom = CHeight - 1;
Camera = geCamera_Create(2.0f, &Rect);
if (!Camera) {
MessageBox(NULL,"No Camera","Error", MB_OK);
_exit(-1);
}
Level = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "levels\\genvs.bsp", NULL, GE_VFILE_OPEN_READONLY);
// | GE_VFILE_OPEN_DIRECTORY);
World = geWorld_Create(Level);
geVFile_Close(Level);
if (!World) {
MessageBox(NULL,"No World","Error", MB_OK);
_exit(-1);
}
if (geEngine_AddWorld(Engine, World)== GE_FALSE)
MessageBox(NULL, "engine cannot use this level", "Error", MB_OK);
Setup();
geXForm3d_SetIdentity(&Xform);
geXForm3d_RotateZ(&Xform, (geFloat)0.0);
geXForm3d_RotateX(&Xform, (geFloat)0.0);
geXForm3d_RotateY(&Xform, (geFloat)0.0);
geXForm3d_Translate(&Xform, (geFloat)0.0, (geFloat)Player.NormalHeight, (geFloat)0.0);
geXForm3d_SetIdentity(&ActorXform);
geXForm3d_RotateZ(&ActorXform, (geFloat)0.0);
geXForm3d_RotateX(&ActorXform, (geFloat)-89.55); //stand the actor upright.
geXForm3d_RotateY(&ActorXform, (geFloat)2.8);
geXForm3d_Translate(&ActorXform, (geFloat)0.0, (geFloat)Player.NormalHeight, (geFloat)0.0);
//load the act file.
ActorFile = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "actors\\dema.act", NULL, GE_VFILE_OPEN_READONLY);
if(ActorFile)
{
//create a definition of the actor
ActorDef = geActor_DefCreateFromFile (ActorFile);
if(ActorDef)
{
Actor = geActor_Create (ActorDef);
//add that actor to the world
geWorld_AddActor (World, Actor, GE_ACTOR_RENDER_NORMAL | GE_ACTOR_COLLIDE, 0xffffffff);
//make the actor bigger.
geActor_SetScale(Actor, 2.3f,2.3f,2.3f);
//extract the motion saved in the actor as "Idle"
Motion = geActor_GetMotionByName(ActorDef, "Idle" );
//set the actors pose (this is called later repeatedly for
//simple animation
geActor_SetPose(Actor, Motion, ModelCounter, &ActorXform);
}
}
geVFile_Close(ActorFile); // Close our file.
geCamera_SetWorldSpaceXForm(Camera, &Xform);
//this loop is intended to be the main rendering and windows message pumping loop
run = 1;
Debuginfo = GE_FALSE;
while (run) {
oldpos = Xform.Translation; //old position
newpos = Xform.Translation; //new position
if (IsKeyDown(' ')) // Jump
{
Jump();
}
if (IsKeyDown('C')) // Crouch
{
Crouch();
}
else // See if we need to Uncrouch
{
if (Player.CurrentHeight < Player.NormalHeight)
UnCrouch();
}
if (IsKeyDown('E')) // Move Forward
{
geXForm3d_GetIn(&Xform, &In); // get forward vector
Move ((float)Player.CurrentSpeed);
}
if (IsKeyDown('D')) // Move Backward
{
geXForm3d_GetIn(&Xform, &In); // get forward vector
Move (-1 * (float)Player.CurrentSpeed); // Multiply the speed by -1 to go
// in reverse along that vector
// i.e. backwards.
}
if (IsKeyDown('S')) // Move Left
{
geXForm3d_GetLeft(&Xform, &In); // Get the left vector
Move ((float)Player.CurrentSpeed);
}
if (IsKeyDown('F')) // Move Right
{
geXForm3d_GetLeft(&Xform, &In); // Get the left vector
Move (-1 * (float)Player.CurrentSpeed); // Multiply the speed by -1 to go
// in reverse along that vector
// i.e. to the right.
}
if (IsKeyDown('P')) // Toggle Printing debug info on the screen.
{
if (Debuginfo == GE_FALSE)
Debuginfo = GE_TRUE;
else
Debuginfo = GE_FALSE;
geEngine_EnableFrameRateCounter(Engine, Debuginfo);
}
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if(Result == 1) //Your new position collides with a wall or object
newpos = oldpos;
ApplyGravity(); //Apply our gravity
MoveHead(); //gather mouse motion data, move camera
ActorXform.Translation = newpos; // Set our Xforms to the new world position.
Xform.Translation = newpos;
// We don't want to be clipping our actor's face so lets fix that here.
geXForm3d_GetUp(&ActorXform, &In);
geVec3d_AddScaled (&ActorXform.Translation, &In, 10.0f, &ActorXform.Translation);
ModelCounter += 0.1f; // Make the actor move.
if(ModelCounter > 5.0)
ModelCounter = 0.0;
geActor_SetPose(Actor, Motion, ModelCounter, &ActorXform); // Repose
tick = GetTickCount() - LastTickCount; // Num of ticks since last loop
if (Debuginfo)
geEngine_Printf (Engine, 1, 151, "TickCount: %d", tick);
while (tick < 70) // Loop until our tick count is approximately right.
tick = GetTickCount() - LastTickCount;
if (Debuginfo)
geEngine_Printf (Engine, 1, 165, "TickCount Compensation: %d", tick);
LastTickCount = GetTickCount();
if (GE_FALSE == geEngine_BeginFrame(Engine, Camera, GE_FALSE)) break;
//render to offscreen buffer
if (GE_FALSE == geEngine_RenderWorld(Engine, World, Camera, 0.0f)) break;
//end the frame, copy offscreen buffer to onscreen window
if (GE_FALSE == geEngine_EndFrame(Engine)) break;
while (PeekMessage( &Msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage(&Msg, NULL, 0, 0 ))
{
run = 0;
break;
}
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
//shut it all down and quit
geCamera_Destroy(&Camera);
geWorld_Free(World);
geEngine_ShutdownDriver(Engine);
geEngine_Free(Engine);
return (0);
}
//=====================================================================================
// CreateMainWindow
//=====================================================================================
static HWND CreateMainWindow(HINSTANCE hInstance, char *AppName, int32 Width, int32 Height)
{
WNDCLASS wc;
HWND hWnd;
RECT ScreenRect;
GetWindowRect(GetDesktopWindow(),&ScreenRect); // Get the size of the screen
//
// Set up and register application window class
//
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = (const char*)NULL;
wc.lpszClassName = AppName;
RegisterClass(&wc);
//
// Create application's main window
//
hWnd = CreateWindowEx(
0, AppName, AppName, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
MessageBox(0, "Could not create window.", "** ERROR **", MB_OK);
_exit(1);
}
UpdateWindow(hWnd);
SetFocus(hWnd);
SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) & ~WS_POPUP);
SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) | (WS_OVERLAPPED |
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX));
SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) | WS_THICKFRAME |
WS_MAXIMIZEBOX);
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_TOPMOST);
SetWindowPos
(hWnd, HWND_TOP,
(ScreenRect.right + ScreenRect.left) /2 - 300,
(ScreenRect.bottom + ScreenRect.top) /2 - 250,
500, 400, SWP_NOCOPYBITS | SWP_NOZORDER);
ShowWindow(hWnd, SW_SHOWNORMAL); // Make window visible
return hWnd;
}
////////////////////////////////////////////////////////////////////////////////////////
void LoadPrefs(int *CWidth, int *CHeight, char *OurDriver)
{
FILE *stream;
if ((stream = fopen ("prefs.ini","r")) == NULL)
{
MessageBox(NULL, "Prefs.ini file not found, using defaults...", "No Prefs", MB_OK);
return;
}
fscanf(stream,"%s", OurDriver);
fscanf(stream,"%d", CWidth);
fscanf(stream,"%d", CHeight);
fclose(stream);
}
//=====================================================================================
// IsKeyDown
//=====================================================================================
static geBoolean IsKeyDown (int KeyCode)
{
if (GetAsyncKeyState(KeyCode) & 0x8000)
return GE_TRUE;
return GE_FALSE;
}
///////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDOWN:
{
PostMessage(hWnd, WM_QUIT, 0, 0);
}
default:
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////
void MoveHead(void) //Moves the camera base on our mouse position
{
POINT temppos;
geVec3d Pos; // this value will be used later when we translate
// our ViewXform.
geVec3d TempAngles;
GetCursorPos(&temppos);
oldpos = newpos;
TempAngles = Angles;
if (ourdriver == 'G') // Glide
SetCursorPos(CWidth/2, (CHeight/2 - 16));
else // DirectX
SetCursorPos(CWidth/2 + 4, (CHeight/2 + 24));
ScreenToClient(mainwindowhandle, &temppos);
if ((temppos.x != pos.x) || (temppos.y != pos.y))
{
if (temppos.x > pos.x) // is it to the left?
{
TempAngles.Y = TempAngles.Y-(geFloat)((temppos.x-(CWidth/2))*.0010); //if so spin left
geXForm3d_RotateY(&ActorXform, TempAngles.Y - Angles.Y);
}
else if (temppos.x < pos.x) // is it to the right?
{
TempAngles.Y = TempAngles.Y+(geFloat)(((CWidth/2)+temppos.x)*.0010); //if so spin right
geXForm3d_RotateY(&ActorXform, TempAngles.Y - Angles.Y);
}
if (temppos.y > pos.y) // is it to the top?
TempAngles.X = TempAngles.X-(geFloat)((temppos.y-(CHeight/2))*.0010); //if so look up
else if (temppos.y < pos.y) // is it to the bottom?
TempAngles.X = TempAngles.X+(geFloat)(((CHeight/2)+temppos.y)*.0010); //if so look down
//make sure we arent looking too far up or down. If we are then fix that!
if (TempAngles.X >0.9f)
TempAngles.X =0.9f;
if (TempAngles.X <-0.9f)
TempAngles.X =-0.9f;
}
Xform.Translation = newpos;
Angles = TempAngles;
// Copy our Xform into ViewXForm (so we can deform it
// to where our player is looking etc)
ViewXForm = Xform;
Pos = ViewXForm.Translation;
geXForm3d_SetIdentity(&ViewXForm); // Clear the matrix
geXForm3d_RotateZ(&ViewXForm, Angles.Z); // Rotate then translate
geXForm3d_RotateX(&ViewXForm, Angles.X);
geXForm3d_RotateY(&ViewXForm, Angles.Y);
geXForm3d_Translate(&ViewXForm, Pos.X, Pos.Y, Pos.Z);
// We give a +140 adjustment to simulate an eye-high view
ViewXForm.Translation.Y += Player.CurrentHeight;
geCamera_SetWorldSpaceXForm(Camera, &ViewXForm);
geCamera_SetAttributes(Camera, 2.0f, &Rect);
}
///////////////////////////////////////////////////////////////////////////////////////
void ApplyGravity(void)
{
geVec3d Up; //will contain the up/down vector for scale, it is the up/down counterpart of In
oldpos = newpos;
geXForm3d_SetYRotation(&Xform, Angles.Y); //which way are we facing?
geXForm3d_GetUp(&Xform, &Up); //get our upward vector
Player.TimeinAir++;
Player.FallingSpeed = -5.6f * (float)Player.TimeinAir;
geVec3d_AddScaled (&oldpos, &Up, Player.FallingSpeed, &newpos); // Move
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if (Result == 1) // Your new position collides with something
{
if (Collision.Plane.Normal.Y < 0.3f) // non-climbable slope
{
geVec3d_AddScaled (&newpos, &Up, -9.6f, &newpos); // Make it a faster slide
Bd = geVec3d_DotProduct (&newpos, &Collision.Plane.Normal) - Collision.Plane.Dist;
newpos.X -= Collision.Plane.Normal.X * Bd;
newpos.Y -= Collision.Plane.Normal.Y * Bd;
newpos.Z -= Collision.Plane.Normal.Z * Bd;
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if(Result == 1) // Your new position collides with something
newpos = Collision.Impact; // set new position to the point of collision.
}
else
{
Player.TimeinAir = 0;
newpos = Collision.Impact;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
void Setup(void)
{
Player.CurrentHeight = 140.0f; // Setup our Player defaults.
Player.CurrentSpeed = 35;
Player.NormalHeight = 140.0f;
Player.NormalSpeed = 35;
Player.TimeinAir = 0;
Player.FallingSpeed = 0.0f;
Mins.X = -20.0f; // Mins/Maxs set up the bounding box around our player which is
Mins.Y = 0.0f; // used for collision detect purposes. It tells it when we are
Mins.Z = -20.0f; // colliding.
Maxs.X = 20.0f;
Maxs.Y = Player.NormalHeight;
Maxs.Z = 20.0f;
}
///////////////////////////////////////////////////////////////////////////////////////
void Jump (void)
{
geVec3d Up; //will contain the up/down vector for scale, it is the up/down counterpart of In
float upspeed = 40.0f; //speed we are raising.
oldpos = Xform.Translation; //old position
newpos = Xform.Translation; //new position
geXForm3d_SetYRotation(&Xform, Angles.Y); //which way are we facing?
geXForm3d_GetUp(&Xform, &Up); //get our upward vector
geVec3d_AddScaled (&oldpos, &Up, upspeed, &newpos); // Move
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if(Result == 1) //Your new position collides with something
{
Xform.Translation = Collision.Impact; // We've hit something.
Player.TimeinAir = Player.TimeinAir + 3; // Force us down.
}
}
///////////////////////////////////////////////////////////////////////////////////////
void Crouch (void)
{
float upspeed = -40.0f; //speed we are lowering.
if (Player.CurrentHeight > 20) // If we can crouch down lower...
{
Player.CurrentHeight = Player.CurrentHeight + upspeed; // Lower our current height.
Maxs.Y = Player.CurrentHeight; // Update our bounding box.
Player.CurrentSpeed = Player.CurrentSpeed - 5; // Slow us down as you probably
// Wouldn't be able to crouch and walk as fast as you would normally walk.
}
}
///////////////////////////////////////////////////////////////////////////////////////
void UnCrouch(void)
{
geVec3d Up; //will contain the up/down vector for scale, it is the up/down counterpart of In
float upspeed = 40.0f; //speed we are raising.
oldpos = Xform.Translation; //old position
newpos = Xform.Translation; //new position
geXForm3d_SetYRotation(&Xform, Angles.Y); //which way are we facing?
geXForm3d_GetUp(&Xform, &Up); //get our upward vector
geVec3d_AddScaled (&oldpos, &Up, upspeed, &newpos); // Move
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if(Result == 1) //Your new position collides with something
{
Xform.Translation = oldpos; // We be hittin' the ceilin'
}
else // We can move up, so lets not do it all at once, nobody pops up that
{ // Quickly!!
Player.CurrentHeight = Player.CurrentHeight + 40; // Increase our current height variable.
Maxs.Y = Player.CurrentHeight; // Reset our bounding box.
Player.CurrentSpeed = Player.CurrentSpeed + 5; // Make us a little faster again.
newpos = oldpos; // Let our CurrentHeight variable change the Xform or else
// we'll bounce when we get to the top.
}
}
///////////////////////////////////////////////////////////////////////////////////////
void Move (float speed)
{
geVec3d_AddScaled (&newpos, &In, speed, &newpos); // Move
Result = geWorld_Collision(World, &Mins, &Maxs, &oldpos, &newpos, GE_CONTENTS_SOLID_CLIP, GE_COLLIDE_ALL,
0, NULL, NULL, &Collision);
if(Result == 1) //Your new position collides with something
{
geVec3d_AddScaled (&newpos, &In, speed, &newpos); // Move
if (Collision.Plane.Normal.Y < 0) // This is really crappy code here
Collision.Plane.Normal.Y = 0; // It allows sliding on reverse
// inclined walls but it's not that
// good. Just a quick hack.
// This is the sliding code used for wall slides. It works well and
// should remain in the program.
Bd = geVec3d_DotProduct (&newpos, &Collision.Plane.Normal) - Collision.Plane.Dist;
newpos.X -= Collision.Plane.Normal.X * Bd;
newpos.Y -= Collision.Plane.Normal.Y * Bd;
newpos.Z -= Collision.Plane.Normal.Z * Bd;
}
}
Prefs.ini File:
(
800
600
Project - X (c) 1999 Brent Orford.