This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/system.c

851 lines
20 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// utils.c - shared launcher utils
//=======================================================================
#include "launch.h"
system_t Sys;
sysinfo_t SI;
stdlib_api_t com;
dll_info_t engine_dll = { "engine.dll", NULL, "CreateAPI", NULL, NULL, 1, sizeof( launch_exp_t ), sizeof( stdlib_api_t ) };
static const char *show_credits = "\n\n\n\n\tCopyright XashXT Group %s ©\n\t\
All Rights Reserved\n\n\t Visit www.xash.ru\n";
// stubs
void NullInit( int argc, char **argv ) {}
void NullFunc( void ) {}
void NullPrint( const char *msg ) {}
void Sys_GetStdAPI( void )
{
// interface validator
com.api_size = sizeof( stdlib_api_t );
com.com_size = sizeof( stdlib_api_t );
// base events
com.instance = Sys_NewInstance;
com.printf = Sys_Msg;
com.dprintf = Sys_MsgDev;
com.error = Sys_Error;
com.abort = Sys_Break;
com.exit = Sys_Exit;
com.print = Sys_Print;
com.Com_CheckParm = Sys_CheckParm; // get parm from cmdline
com.Com_GetParm = Sys_GetParmFromCmdLine; // get argument for specified parm
com.input = Con_Input;
com.SysInfo = &SI;
}
/*
==================
Parse program name to launch and determine work style
NOTE: at this day we have ten instances
0. "offline" - invalid instance
1. "credits" - show engine credits
2. "dedicated" - dedicated server
3. "normal" - normal or dedicated game launch
==================
*/
void Sys_LookupInstance( void )
{
char szTemp[128];
qboolean dedicated = false;
Sys.app_name = HOST_OFFLINE;
// we can specified custom name, from Sys_NewInstance
if( GetModuleFileName( NULL, szTemp, sizeof( szTemp )) && Sys.app_state != SYS_RESTART )
Com_FileBase( szTemp, SI.ModuleName );
// determine host type
if( SI.ModuleName[0] == '#' || SI.ModuleName[0] == '©' )
{
if( SI.ModuleName[0] == '#' ) dedicated = true;
if( SI.ModuleName[0] == '©' ) strcpy( Sys.progname, "credits" );
// cutoff hidden symbols
strncpy( szTemp, SI.ModuleName + 1, sizeof( szTemp ));
strncpy( SI.ModuleName, szTemp, sizeof( SI.ModuleName ));
}
if( Sys.progname[0] == '$' )
{
// custom path came from executable, otherwise can't be modified
strncpy( SI.ModuleName, Sys.progname + 1, sizeof( SI.ModuleName ));
strncpy( Sys.progname, "normal", sizeof( Sys.progname )); // set as "normal"
}
// lookup all instances
if( !strcmp( Sys.progname, "credits" ))
{
Sys.app_name = HOST_CREDITS; // easter egg
Sys.linked_dll = NULL; // no need to loading library
Sys.log_active = Sys.developer = 0; // clear all dbg states
strcpy( Sys.caption, "About" );
Sys.con_showcredits = true;
}
else if( !strcmp( Sys.progname, "normal" ))
{
if( dedicated )
{
Sys.app_name = HOST_DEDICATED;
Sys.con_readonly = false;
// check for duplicate dedicated server
Sys.hMutex = CreateMutex( NULL, 0, "Xash Dedicated Server" );
if( !Sys.hMutex )
{
MSGBOX( "Dedicated server already running" );
Sys_Exit();
return;
}
CloseHandle( Sys.hMutex );
Sys.hMutex = CreateSemaphore( NULL, 0, 1, "Xash Dedicated Server" );
if( !Sys.developer ) Sys.developer = 3; // otherwise we see empty console
strcpy( Sys.log_path, "dedicated.log" );
}
else
{
Sys.app_name = HOST_NORMAL;
Sys.con_readonly = true;
// don't show console as default
if( Sys.developer < D_WARN ) Sys.con_showalways = false;
strcpy( Sys.log_path, "engine.log" );
}
Sys.linked_dll = &engine_dll; // pointer to engine.dll info
strcpy( Sys.caption, "Xash3D" );
}
// share instance over all system
SI.instance = Sys.app_name;
}
/*
==================
Find needed library, setup and run it
==================
*/
void Sys_CreateInstance( void )
{
// export
launch_t CreateHost;
launch_exp_t *Host; // callback to mainframe
srand( time( NULL )); // init random generator
Sys_LoadLibrary( NULL, Sys.linked_dll ); // loading library if need
// pre initializations
switch( Sys.app_name )
{
case HOST_NORMAL:
case HOST_DEDICATED:
CreateHost = (void *)Sys.linked_dll->main;
Host = CreateHost( &com, NULL ); // second interface not allowed
Sys.Init = Host->Init;
Sys.Main = Host->Main;
Sys.Free = Host->Free;
Sys.CPrint = Host->CPrint;
Sys.CmdAuto = Host->CmdComplete;
Sys.Crashed = Host->Crashed;
break;
case HOST_CREDITS:
Sys_Break( show_credits, timestamp( TIME_YEAR_ONLY ));
break;
case HOST_OFFLINE:
Sys_Break( "Host offline\n" );
break;
}
// init our host now!
Sys.Init( Sys.argc, Sys.argv );
// post initializations
if( Sys.app_name == HOST_NORMAL ) Con_ShowConsole( false ); // hide console
Sys.app_state = SYS_FRAME; // system is now active
}
/*
==================
Sys_ParseCommandLine
==================
*/
void Sys_ParseCommandLine( LPSTR lpCmdLine )
{
Sys.argc = 1;
Sys.argv[0] = "exe";
while( *lpCmdLine && ( Sys.argc < MAX_NUM_ARGVS ))
{
while( *lpCmdLine && *lpCmdLine <= ' ' )
lpCmdLine++;
if( !*lpCmdLine ) break;
if( *lpCmdLine == '\"' )
{
// quoted string
lpCmdLine++;
Sys.argv[Sys.argc] = lpCmdLine;
Sys.argc++;
while( *lpCmdLine && ( *lpCmdLine != '\"' ))
lpCmdLine++;
}
else
{
// unquoted word
Sys.argv[Sys.argc] = lpCmdLine;
Sys.argc++;
while( *lpCmdLine && *lpCmdLine > ' ')
lpCmdLine++;
}
if( *lpCmdLine )
{
*lpCmdLine = 0;
lpCmdLine++;
}
}
}
/*
==================
Sys_MergeCommandLine
==================
*/
void Sys_MergeCommandLine( LPSTR lpCmdLine )
{
const char *blank = "censored";
int i;
for( i = 0; i < Sys.argc; i++ )
{
// we wan't return to first game
if( !stricmp( "-game", Sys.argv[i] )) Sys.argv[i] = (char *)blank;
// probably it's timewaster, because engine rejected second change
if( !stricmp( "+game", Sys.argv[i] )) Sys.argv[i] = (char *)blank;
// you sure what is map exists in new game?
if( !stricmp( "+map", Sys.argv[i] )) Sys.argv[i] = (char *)blank;
// just stupid action
if( !stricmp( "+load", Sys.argv[i] )) Sys.argv[i] = (char *)blank;
// changelevel beetwen games? wow it's great idea!
if( !stricmp( "+changelevel", Sys.argv[i] )) Sys.argv[i] = (char *)blank;
// second call
if( Sys.app_name == HOST_DEDICATED && !strnicmp( "+menu_", Sys.argv[i], 6 ))
Sys.argv[i] = (char *)blank;
}
}
/*
================
Sys_Print
print into window console
================
*/
void Sys_Print( const char *pMsg )
{
const char *msg;
char buffer[32768];
char logbuf[32768];
char *b = buffer;
char *c = logbuf;
int i = 0;
if( Sys.con_silentmode ) return;
if( Sys.CPrint && Sys.app_name == HOST_NORMAL )
Sys.CPrint( pMsg );
// if the message is REALLY long, use just the last portion of it
if( strlen( pMsg ) > sizeof( buffer ) - 1 )
msg = pMsg + strlen( pMsg ) - sizeof( buffer ) + 1;
else msg = pMsg;
// copy into an intermediate buffer
while( msg[i] && (( b - buffer ) < sizeof( buffer ) - 1 ))
{
if( msg[i] == '\n' && msg[i+1] == '\r' )
{
b[0] = '\r';
b[1] = c[0] = '\n';
b += 2, c++;
i++;
}
else if( msg[i] == '\r' )
{
b[0] = c[0] = '\r';
b[1] = '\n';
b += 2, c++;
}
else if( msg[i] == '\n' )
{
b[0] = '\r';
b[1] = c[0] = '\n';
b += 2, c++;
}
else if( msg[i] == '\35' || msg[i] == '\36' || msg[i] == '\37' )
{
i++; // skip console pseudo graph
}
else if( IsColorString( &msg[i] ))
{
i++; // skip color prefix
}
else
{
*b = *c = msg[i];
b++, c++;
}
i++;
}
*b = *c = 0; // cutoff garbage
// because we needs to kill any psedo graph symbols
// and color strings for other instances
if( Sys.CPrint && Sys.app_name != HOST_NORMAL )
Sys.CPrint( logbuf );
Sys_PrintLog( logbuf );
Con_Print( buffer );
}
/*
================
Sys_Msg
formatted message
================
*/
void Sys_Msg( const char *pMsg, ... )
{
va_list argptr;
char text[8192];
va_start( argptr, pMsg );
_vsnprintf( text, sizeof( text ), pMsg, argptr );
va_end( argptr );
Sys_Print( text );
}
void Sys_MsgDev( int level, const char *pMsg, ... )
{
va_list argptr;
char text[8192];
if( Sys.developer < level ) return;
va_start( argptr, pMsg );
_vsnprintf( text, sizeof( text ), pMsg, argptr );
va_end( argptr );
switch( level )
{
case D_WARN:
Sys_Print( va( "^3Warning:^7 %s", text ));
break;
case D_ERROR:
Sys_Print( va( "^1Error:^7 %s", text ));
break;
case D_INFO:
case D_NOTE:
case D_AICONSOLE:
Sys_Print( text );
break;
}
}
/*
================
Sys_DoubleTime
================
*/
double Sys_DoubleTime( void )
{
static LARGE_INTEGER g_PerformanceFrequency;
static LARGE_INTEGER g_ClockStart;
LARGE_INTEGER CurrentTime;
if( !g_PerformanceFrequency.QuadPart )
{
QueryPerformanceFrequency( &g_PerformanceFrequency );
QueryPerformanceCounter( &g_ClockStart );
}
QueryPerformanceCounter( &CurrentTime );
return (double)( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / (double)( g_PerformanceFrequency.QuadPart );
}
/*
================
Sys_CheckParm
Returns the position (1 to argc-1) in the program's argument list
where the given parameter apears, or 0 if not present
================
*/
int Sys_CheckParm( const char *parm )
{
int i;
for( i = 1; i < Sys.argc; i++ )
{
// NEXTSTEP sometimes clears appkit vars.
if( !Sys.argv[i] ) continue;
if( !stricmp( parm, Sys.argv[i] )) return i;
}
return 0;
}
/*
================
Sys_GetParmFromCmdLine
Returns the argument for specified parm
================
*/
qboolean Sys_GetParmFromCmdLine( char *parm, char *out, size_t size )
{
int argc = Sys_CheckParm( parm );
if( !argc ) return false;
if( !out ) return false;
if( !Sys.argv[argc + 1] ) return false;
strncpy( out, Sys.argv[argc+1], size );
return true;
}
/*
================
Sys_Sleep
freeze application for some time
================
*/
void Sys_Sleep( int msec )
{
msec = bound( 1, msec, 1000 );
Sleep( msec );
}
/*
================
Sys_WaitForQuit
wait for 'Esc' key will be hit
================
*/
void Sys_WaitForQuit( void )
{
MSG msg;
Con_RegisterHotkeys();
msg.message = 0;
// wait for the user to quit
while( msg.message != WM_QUIT )
{
if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else Sys_Sleep( 20 );
}
}
/*
================
Sys_Error
NOTE: we must prepare engine to shutdown
before call this
================
*/
void Sys_Error( const char *error, ... )
{
va_list argptr;
char text[MAX_SYSPATH];
if( Sys.app_state == SYS_ERROR )
return; // don't multiple executes
// make sure what console received last message
// stupid windows bug :(
if( Sys.app_state == SYS_RESTART )
Sys_Sleep( 200 );
Sys.error = true;
Sys.app_state = SYS_ERROR;
va_start( argptr, error );
vsprintf( text, error, argptr );
va_end( argptr );
if( Sys.app_name == HOST_NORMAL )
Sys.Free(); // kill video
if( Sys.developer > 0 )
{
Con_ShowConsole( true );
Con_DisableInput(); // disable input line for dedicated server
Sys_Print( text ); // print error message
Sys_WaitForQuit();
}
else
{
Con_ShowConsole( false );
MSGBOX( text );
}
Sys_Exit();
}
void Sys_Break( const char *error, ... )
{
va_list argptr;
char text[MAX_SYSPATH];
if( Sys.app_state == SYS_ERROR )
return; // don't multiple executes
va_start( argptr, error );
vsprintf( text, error, argptr );
va_end( argptr );
Sys.error = true;
Sys.app_state = SYS_ERROR;
if( Sys.app_name == HOST_NORMAL )
Sys.Free(); // kill video
if( Sys.con_readonly && ( Sys.developer > 0 || Sys.app_name != HOST_NORMAL ))
{
Con_ShowConsole( true );
Sys_Print( text );
Sys_WaitForQuit();
}
else
{
Con_ShowConsole( false );
MSGBOX( text );
}
Sys_Exit();
}
long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo )
{
// save config
if( Sys.app_state != SYS_CRASH )
{
// check to avoid recursive call
Sys.error = true;
Sys.app_state = SYS_CRASH;
if( Sys.app_name == HOST_NORMAL && Sys.Crashed ) Sys.Crashed(); // tell client about crash
Msg( "Sys_Crash: call %p at address %p\n", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode );
if( Sys.developer <= 0 )
{
// no reason to call debugger in release build - just exit
Sys_Exit();
return EXCEPTION_CONTINUE_EXECUTION;
}
// all other states keep unchanged to let debugger find bug
Con_DestroyConsole();
}
if( Sys.oldFilter )
return Sys.oldFilter( pInfo );
return EXCEPTION_CONTINUE_EXECUTION;
}
void Sys_Init( void )
{
MEMORYSTATUS lpBuffer;
char dev_level[4];
lpBuffer.dwLength = sizeof( MEMORYSTATUS );
GlobalMemoryStatus( &lpBuffer );
Sys.logfile = NULL;
// get current hInstance
Sys.hInstance = (HINSTANCE)GetModuleHandle( NULL );
Sys.developer = 0;
Sys_GetStdAPI();
Sys.Init = NullInit;
Sys.Main = NullFunc;
Sys.Free = NullFunc;
Sys.CPrint = NullPrint;
Sys.oldFilter = SetUnhandledExceptionFilter( Sys_Crash );
// some commands may turn engine into infinity loop,
// e.g. xash.exe +game xash -game xash
// so we clearing all cmd_args, but leave dbg states as well
if( Sys.app_state != SYS_RESTART )
Sys_ParseCommandLine( GetCommandLine());
else Sys_MergeCommandLine( GetCommandLine());
// parse and copy args into local array
if( Sys_CheckParm( "-log" )) Sys.log_active = true;
if( Sys_CheckParm( "-console" )) Sys.developer = 1;
if( Sys_CheckParm( "-dev" ))
{
if( Sys_GetParmFromCmdLine( "-dev", dev_level, sizeof( dev_level )))
{
if( isdigit( dev_level[0] ))
Sys.developer = abs( atoi( dev_level ));
else Sys.developer++; // -dev == 1, -dev -console == 2
}
else Sys.developer++; // -dev == 1, -dev -console == 2
}
if( Sys.log_active && !Sys.developer )
Sys.log_active = false; // nothing to logging :)
SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors
// set default state
Sys.con_showalways = Sys.con_readonly = true;
Sys.con_showcredits = Sys.con_silentmode = false;
Sys_LookupInstance(); // init launcher
Con_CreateConsole();
// second pass (known state)
if( Sys.app_state == SYS_RESTART )
Sys_MergeCommandLine( GetCommandLine());
// first text message into console or log
MsgDev( D_NOTE, "Sys_LoadLibrary: Loading launch.dll - ok\n" );
if( strlen( Sys.fmessage ) && !Sys.con_showcredits )
{
Sys_Print( Sys.fmessage );
Sys.fmessage[0] = '\0';
}
SI.developer = Sys.developer;
Sys_CreateInstance();
}
void Sys_Shutdown( void )
{
// prepare host to close
Sys.Free();
Sys_FreeLibrary( Sys.linked_dll );
Sys.CPrint = NullPrint;
Con_DestroyConsole();
// restore filter
if( Sys.oldFilter )
{
SetUnhandledExceptionFilter( Sys.oldFilter );
}
}
/*
================
Sys_Exit
NOTE: we must prepare engine to shutdown
before call this
================
*/
void Sys_Exit( void )
{
if( Sys.shutdown_issued ) return;
Sys.shutdown_issued = true;
if( Sys.app_state != SYS_ERROR )
Sys.app_state = SYS_SHUTDOWN;
Sys_Shutdown();
exit( Sys.error );
}
//=======================================================================
// DLL'S MANAGER SYSTEM
//=======================================================================
qboolean Sys_LoadLibrary( const char *dll_name, dll_info_t *dll )
{
const dllfunc_t *func;
qboolean native_lib = false;
string errorstring;
// check errors
if( !dll ) return false; // invalid desc
if( dll->link ) return true; // already loaded
// check and replace names
if( dll_name && *dll_name ) dll->name = dll_name;
if( !dll->name || !*dll->name ) return false; // nothing to load
MsgDev( D_NOTE, "Sys_LoadLibrary: Loading %s", dll->name );
if( dll->fcts )
{
// lookup export table
for( func = dll->fcts; func && func->name != NULL; func++ )
*func->func = NULL;
}
else if( dll->entry ) native_lib = true;
if( !dll->link ) dll->link = LoadLibrary ( dll->name ); // environment pathes
// no DLL found
if( !dll->link )
{
sprintf( errorstring, "Sys_LoadLibrary: couldn't load %s\n", dll->name );
goto error;
}
if( native_lib )
{
if(( dll->main = Sys_GetProcAddress( dll, dll->entry )) == 0 )
{
sprintf( errorstring, "Sys_LoadLibrary: %s has no valid entry point\n", dll->name );
goto error;
}
}
else
{
// Get the function adresses
for( func = dll->fcts; func && func->name != NULL; func++ )
{
if( !( *func->func = Sys_GetProcAddress( dll, func->name )))
{
sprintf( errorstring, "Sys_LoadLibrary: %s missing or invalid function (%s)\n", dll->name, func->name );
goto error;
}
}
}
if( native_lib )
{
generic_api_t *check = NULL;
// NOTE: native dlls must support null import!
// e.g. see ..\engine\engine.c for details
check = (void *)dll->main( &com, NULL ); // first iface always stdlib_api_t
if( !check )
{
sprintf( errorstring, "Sys_LoadLibrary: \"%s\" have no export\n", dll->name );
goto error;
}
if( check->api_size != dll->api_size )
{
sprintf( errorstring, "Sys_LoadLibrary: \"%s\" mismatch interface size (%i should be %i)\n", dll->name, check->api_size, dll->api_size );
goto error;
}
if( check->com_size != dll->com_size )
{
sprintf( errorstring, "Sys_LoadLibrary: \"%s\" mismatch stdlib api size (%i should be %i)\n", dll->name, check->com_size, dll->com_size);
goto error;
}
}
MsgDev( D_NOTE, " - ok\n" );
return true;
error:
MsgDev( D_NOTE, " - failed\n" );
Sys_FreeLibrary( dll ); // trying to free
if( dll->crash ) Sys_Error( errorstring );
else MsgDev( D_ERROR, errorstring );
return false;
}
void* Sys_GetProcAddress( dll_info_t *dll, const char* name )
{
if( !dll || !dll->link ) // invalid desc
return NULL;
return (void *)GetProcAddress( dll->link, name );
}
qboolean Sys_FreeLibrary( dll_info_t *dll )
{
// invalid desc or alredy freed
if( !dll || !dll->link )
return false;
if( Sys.app_state == SYS_CRASH )
{
// we need to hold down all modules, while MSVC can find error
MsgDev( D_NOTE, "Sys_FreeLibrary: hold %s for debugging\n", dll->name );
return false;
}
else MsgDev( D_NOTE, "Sys_FreeLibrary: Unloading %s\n", dll->name );
FreeLibrary( dll->link );
dll->link = NULL;
return true;
}
/*
================
Sys_NewInstance
restarted engine with new instance
e.g. for change game or fallback to dedicated mode
================
*/
void Sys_NewInstance( const char *name, const char *fmsg )
{
string tmp;
// save parms
strncpy( tmp, name, sizeof( tmp ));
strncpy( Sys.fmessage, fmsg, sizeof( Sys.fmessage ));
Sys.app_state = SYS_RESTART; // set right state
Sys_Shutdown(); // shutdown current instance
// restore parms here
strncpy( SI.ModuleName, tmp, sizeof( SI.ModuleName ));
// NOTE: we never return to old instance,
// because Sys_Exit call exit(0); and terminate program
Sys_Init();
Sys.Main();
Sys_Exit();
}
// main DLL entry point
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
return TRUE;
}
/*
=================
Main Entry Point
=================
*/
EXPORT int CreateAPI( const char *hostname, qboolean console )
{
strncpy( Sys.progname, hostname, sizeof( Sys.progname ));
Sys_Init();
Sys.Main();
Sys_Exit();
return 0;
}